Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Who is moving my transform?

Discussion in 'Scripting' started by kru, Dec 20, 2016.

  1. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    (Or, "Why public setters that I don't control are costing me money")

    I need a good way to track the setting of transforms.

    I've got a large project that has many different scripts all looking at Transform's getters, and lots of them using the setters. This has spiraled out into a nightmare. Many of the most confusing, and time-consuming bugs throughout the project have been tracking down the reason why an object is being repositioned, or rotated in unexpected ways at unexpected times.

    It is especially egregious during the first few scene-initialization frames, when the world is being built and edited according to the game state. Several different things are operating on the same object at roughly the same time, without a well defined order. The positions of objects are being set multiple times within the same frame by various outside sources. I need to know who is doing it.

    What I would very much enjoy having is a way to produce a stack trace for each call to the transform.position setter, or transform.rotation setter. Then I could parse the log files to determine which systems are operating out of order, and fix them. (Note: I can't easily place my own logs and breakpoints at this point, since the project has 2292 usages of transform.position, plus an unknown number of third-party scripts compiled into dlls).

    So, I am wondering how other developers keep their teams from stomping on each other's transform.positions. I would enjoy suggestions on how other projects debug the collisions between transform changes.

    One thought that I had for future projects is to run all transform changes through a custom SafeTransform class that just wraps the MonoBehaviour transform members. Changing a transform directly will be forbidden by convention. Anyone who does so will be summarily humiliated. Then I have the power to add logs and breakpoints to debug out which systems are moving objects unexpectedly.

    But maybe there is a better way?
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I'm sorry. I moved your transform. I will stop doing it. :p

    I tend to run with a convention that only one component gets to move the transform, and everything else has to go through that component. Works most of the time. Unless I get lazy. Then it doesn't work.

    Their are other conventions that work too. Some of the bigger studios only use MonoBehaviour for hooks, and have all of the game logic implemented in C# classes. This eliminates the tendency to just grab .transform or .gameObject whenever you feel like it. And it also means that there is a natural debug point for anything that touches transform or gameObject.

    The other convention I've used is that transform.position is never set directly (except on initialisation). Its always set relative to its existing value. This means that several scripts can be operating on the same transform without interfering with one another.
     
  3. Lord-Smingleigh

    Lord-Smingleigh

    Joined:
    Jan 23, 2013
    Posts:
    10
    If you're using Visual Studio, mark the property with [ObsoleteAttribute] and Visual Studio will spray non-breaking warnings everywhere the property is referenced. Once you've cleaned up the references you don't want, you can remove the attribute.
     
  4. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Mark a GameObject's transform property with an attribute? Sure, I guess you just decompile the class...seems like an awful lot of work to do the equivalent of a Find.

    What I would do if Transform positions got to be a problem is to implement an interface class that you route your transform position changes through. Then, it's easy to log anything going through that class. Anything that's not going through it is easy to search for. It'd be easiest to do this as an extension method...like setting up extension methods for GetPosition and SetPosition.
     
    Kiwasi likes this.
  5. Lord-Smingleigh

    Lord-Smingleigh

    Joined:
    Jan 23, 2013
    Posts:
    10
    Oh, I thought it was a property of a Monobehaviour. Ignore my foolish rambling.
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    There is a property on both Component (ie MonoBehaviour) and on GameObject. There is no promise which one was used, or that marking one will fix the other.

    In either case you have to decompile to get access to the property. Its a pretty extreme route.

    Unless there is some C# magic I'm unaware of to mark a property with an attribute without having access to the property.
     
  7. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    This is probably not a great way to do it, but if I was desperate... you could make an extension method on Transform like:
    Code (csharp):
    1. public static PositionWrapper positionTempHack(this Transform tr) {
    2.   Debug.Log("Stack trace and whatever other info you need.");
    3.   return new PositionWrapper(tr);
    4. }
    where PositionWrapper is something like:

    Code (csharp):
    1. public class PositionWrapper {
    2.   private Transform transform;
    3.   public Vector3 Position { set { transform.position = value; } }
    4.  
    5.   public PositionWrapper(Transform tr) { transform = tr; }
    6. }
    Then do a global Find and Replace on "transform.position = " to "transform.positionTempHack().Position = ".
    Some of the verbosity of the wrapper there because you can't make an "extension property", just extension methods, and this makes it easier to Find and Replace. Then after you track down what's wrong, reverse the Find and Replace. And for God's sake don't accidentally check in the code with the hack still in place or everyone will think you're terrible. ;)
     
    EthanFischerICS likes this.
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You'll also need a defence against cached transforms too.

    In other words definitely don't do this a a branch that will ever see a commit.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    I spend SO much professional energy trying to keep my own lazy out of my codebase.

    ALSO, regards the OP, anytime you have lots of stray events fired hither and yon from inside Unity, which already treats all your disparate objects as a randomly-ordered collection receiving their Update() events, you probably want to rethink using events to decouple.

    Like @BoredMormon said, I try to have only one controller moving any particular thing, and from the complexity of what you describe, you probably want a master controller that sequences their updates so you can control order.

    One way is to replace the Update() function with MyUpdate() and then explicitly call them in the order you want from a central controller that can fire them in a reliable order, both relative to each other and relative to other types of objects.

    For instance I might have all my enemies throw themselves into a table that gets iterated first, then any enemy that creates a bullet fired at the player will register the bullet script in a second table, that is processed separately and after enemies.

    The main advantage to this approach is for debugging and logging. You can develop a clear and repeatable notion of how the flow of your program is going.
     
  10. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    That's true, you could do a find and replace on just ".position = " in that case, and try to make sure you're only modifying transforms. VS might even have a way to filter it to just transforms. But yeah... like I said, it's a really messy way of doing it, but I don't think there's any easy way to hook into the setter of Transform.position without hacking the Unity dll's.
     
    Kiwasi likes this.
  11. Riderfan

    Riderfan

    Joined:
    Jan 10, 2013
    Posts:
    514
    I know this is an old post, but I was running into a situation similar to the OP. Something, somewhere was changing my object's positions and they were warping into new locations - seemingly at random.

    The extensions approach to intercept the setting/getting of the transform position really helped because it allowed me to find the cause. Bug killed.

    I know that Unity has changed the whole concept of how gameobjects work, so maybe this is a bit of a moot point now, but being able to override transforms and vectors would have really helped.

    thanks to whomever provided the extension code.
     
  12. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I know a very dumb but effective solution to this.
    Simply remove the gameObject from the hierarchy at runtime.
    Scripts that access transform will throw a null ref exception.

    Necroing posts is evil btw.
     
    Kurt-Dekker likes this.
  13. Riderfan

    Riderfan

    Joined:
    Jan 10, 2013
    Posts:
    514
    That will only ever throw exceptions, it won't let you step through code.
     
  14. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Well, its enough to figure out what method attempts to modify the position (via call stack).
    And that is working quite well for me.

    (Not that it happens often)
     
    tashtan likes this.
  15. Riderfan

    Riderfan

    Joined:
    Jan 10, 2013
    Posts:
    514
    And which one of those methods was causing the problem? A call stack only tells you so much you need to be able to step through code and catch the position change when it happens. Hence the need for the extension.
     
  16. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    It also contains a line in the code. (At least on Windows it does)

    I'm not saying its not a valid approach. I'm saying it could be an overkill.
     
    Joe-Censored likes this.
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    With source control there is a broad class of problems (particularly when you are working in an unfamiliar Unity project) where this approach is absolutely the best and fastest way to getting the intel you need to fix it.
     
  18. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Oddly enough, that's how most of biology is done as a science. Remove something and see what happens.
     
  19. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    This kills the GameObject.
     
  20. AndyBlock

    AndyBlock

    Joined:
    Oct 12, 2016
    Posts:
    30
    And for your sanity's sake, don't replace the 'transform.position = ' that you just added in PositionWrapper, unless you like infinite recursion :D .
     
    EthanFischerICS likes this.
  21. ChrisHandzlik

    ChrisHandzlik

    Joined:
    Dec 2, 2016
    Posts:
    203
    I've been in the same boat and put together a tool that weaves IL into your assembly that essentially redirects all calls from `transform.postion = ` (or rotation/scale) to `Interceptor.Set(transform, newValue)` - this is then directly controlled by you and you can get all info necessary. **Like which component exactly is doing that change, with fill stack trace and other parameters.**

    That's all done automatically without modifying your source code.


    You can get it on the asset store!

    There's also an open source component that you can use which does similar thing (although it's not so user-friendly)
    https://github.com/handzlikchris/Unity.TransformSetterInterceptor

    PS: I know this thread is bit old but it still comes up when searching, thought it'd be good to share here.
     
    Last edited: Nov 15, 2021
  22. ChrisHandzlik

    ChrisHandzlik

    Joined:
    Dec 2, 2016
    Posts:
    203
    john-wallace likes this.
  23. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    @ChrisHandzlik i bought your asset and it doesn’t work. It fails every time you hit play in Unity 2022 when initializing. Also you say “contact me” and your site has no contact info. Your LinkedIn is private and we can’t contact you there.

    We also want to monitor one object that is instantiated as a prefab. It seems your interface only works if you have it in memory on the main scene, if you instantiate it from resources, it doesn’t even show up on your UI
     
  24. ChrisHandzlik

    ChrisHandzlik

    Joined:
    Dec 2, 2016
    Posts:
    203
    Hi, @creat327, I think you've managed to find email as I've got a support request - I've sent you some suggestions, let's pick it up there.

    Btw - the contact is in tool window (sorry if that's not visible enough)
    upload_2023-9-11_11-27-2.png