Search Unity

transform.hasChanged is always TRUE!?!?

Discussion in 'Editor & General Support' started by ricarious, May 7, 2019.

  1. ricarious

    ricarious

    Joined:
    Nov 1, 2017
    Posts:
    21
    I created a default scene, created a sphere, and created a script on the Sphere that simply checks
    transform.hasChanged
    every frame of the Update.

    Why is this returning TRUE every frame?

    Code (CSharp):
    1. using UnityEngine;
    2. public class TestHasChanged : MonoBehaviour
    3. {
    4.     void Start() { }
    5.  
    6.     void Update()
    7.     {
    8.         if (transform.hasChanged) Debug.Log("Sphere hasChanged = TRUE!");
    9.         else Debug.Log("Sphere hasChanged = FALSE!");
    10.     }
    11. }
     
  2. ricarious

    ricarious

    Joined:
    Nov 1, 2017
    Posts:
    21
    So the one question is who's job is it to set hasChanged to FALSE? I assume that this is handled internally by Unity and not my job. Right?


    Transform.hasChanged

    public bool hasChanged;

    Description
    Has the transform changed since the last time the flag was set to 'false'?

    A change to the transform can be anything that can cause its matrix to be recalculated: any adjustment to its position, rotation or scale. Note that operations which can change the transform will not actually check if the old and new value are different before setting this flag. So setting, for instance, transform.position will always set hasChanged on the transform, regardless of there being any actual change.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ExampleClass : MonoBehaviour
    5. {
    6.     void Update()
    7.     {
    8.         if (transform.hasChanged)
    9.         {
    10.             print("The transform has changed!");
    11.             transform.hasChanged = false;
    12.         }
    13.     }
    14. }
     
    Odminnimda likes this.
  3. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    From what the documentation is saying, it does seem like you have to manually set this value to false, which is a bit strange to me.
     
    stefan-velnita likes this.
  4. ricarious

    ricarious

    Joined:
    Nov 1, 2017
    Posts:
    21
    I believe that I now understand how
    Transform.hasChanged
    works and how it was intended to be used.

    My original mistake was believing that
    Transform.hasChanged
    was completely managed by Unity (i.e. Unity automatically: a) sets
    hasTransform=true
    any time the Transform is changed (duh) AND b) would reset to false at the end of every frame.). IOW you could call
    hasChanged
    on any Transform and expect it to reflect changes made this frame. But that appears to be 1/2 wrong, but the important half...

    I now believe that the
    Transform.hasTransform
    field is an unmanaged convenience 'dirty bit' to be managed by the application. As expected, Unity will set
    hasChanged=true
    when changes are made to the Transform. But, that's it. Your job (application) is to manage this frame-by-frame yourself (i.e. set
    hasChanged=false
    each frame after you're done checking it).

    Please correct me if I have this wrong.
     
  5. ricarious

    ricarious

    Joined:
    Nov 1, 2017
    Posts:
    21
    btw: If anyone is having confusion or problems with Transform.hasChanged, I am happy to help. Just PM me.
     
  6. Glurth

    Glurth

    Joined:
    Dec 29, 2014
    Posts:
    109
    I think you have this correct. Here is a simple component I typed up, but have not really tested much, yet. It simply resets the hasChanged flag at the end of the Frame. Obviously, you'd need to add this component to any GameObject whose transform.hasChanged flag you want to check.

    Code (CSharp):
    1. public class TransformHasChangedByFrame : MonoBehaviour
    2. {
    3.     IEnumerator coroutine;
    4.  
    5.     void Start()
    6.     {
    7.         StartCoroutine(coroutine);
    8.     }
    9.  
    10.     IEnumerator EndOfFrameReset()
    11.     {
    12.         while (true)
    13.         {
    14.             yield return new WaitForEndOfFrame();
    15.             transform.hasChanged = false;//set to false so we can detect the next time it changes
    16.         }
    17.         yield return null;
    18.     }
    19. }
     
  7. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,300
    This is correct. We only set it to true. You need to manage when it is set to false. If you belive the docs are not clear enough then please use the feedback form at the bottom of the page.
     
  8. Glurth

    Glurth

    Joined:
    Dec 29, 2014
    Posts:
    109
    I expanded upon the above version- this one works on a per-object basis and returns true if the transform hasChanged since the last time this particular object checked it. I've done some testing, but not extensive.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. /// <summary>
    6. /// Add this component to a GameObject to make it's transform.hasChanged flag checkable, on a per object basis.  Meaning, by passing an object (usually the calling component), the HasChangedByObject function will return true if the transform has changed since the last time this particular object checked it.
    7. /// </summary>
    8. public class PerObjectTransformChangeMonitor : MonoBehaviour
    9. {
    10.     bool hasEverChanged=false;  //until the ctransform has changed at least once, none of the perObject stuff is needed
    11.     HashSet<object> objectsThatCheckedSinceLastTimeChanged= new HashSet<object>();
    12.  
    13.     /// <summary>
    14.     /// This function will return the HasChanged value of this component's transform, uniquely for the 'whosAsking' object passed to this function.
    15.     /// So, calling this function after the transform has changed, with the same object passed as the parameter- will yield true only the first time it's called, and false thereafter (until the transform changes again).
    16.     /// But calling the function with different object parameters, would yield true for each different object passed (the first time is is passed- untill the transform changes again).
    17.     /// </summary>
    18.     /// <param name="whosAsking">This object will be noted internally, incase it asks again before the transform has actually changed.</param>
    19.     /// <returns>True if the transform has changed since the last time the whosAsking object checked. False otherwise.</returns>
    20.     public bool HasChangedByObject(object whosAsking)
    21.     {
    22.         if (!hasEverChanged) return false;
    23.         if (objectsThatCheckedSinceLastTimeChanged.Contains(whosAsking)) return false;
    24.         objectsThatCheckedSinceLastTimeChanged.Add(whosAsking);
    25.         return true;
    26.  
    27.     }
    28.  
    29.     IEnumerator coroutine;
    30.     void Start()
    31.     {
    32.         transform.hasChanged = false;
    33.         coroutine = EndOfFrameReset();
    34.         StartCoroutine(coroutine);
    35.     }
    36.  
    37.     IEnumerator EndOfFrameReset()
    38.     {
    39.         while (true)
    40.         {
    41.             yield return new WaitForEndOfFrame();
    42.             if(transform.hasChanged)
    43.             {
    44.                 objectsThatCheckedSinceLastTimeChanged.Clear();
    45.                 hasEverChanged = true;
    46.             }
    47.             transform.hasChanged = false;//set to false so we can detect the next time it changes
    48.         }
    49.         yield return null;
    50.     }
    51. }
    52.  
    My testing component/usage example:
    Code (CSharp):
    1. public class ChangeMonitorTester : MonoBehaviour
    2. {
    3.     public bool detectOnlyOnKeydown = false;
    4.     public PerObjectTransformChangeMonitor monitored;
    5.  
    6.     void Update()
    7.     {
    8.         if(!detectOnlyOnKeydown || Input.anyKeyDown )
    9.             if (monitored.HasChangedByObject(this))
    10.                 Debug.Log("Transform change in "+monitored.name + " detected by " + name);
    11.     }
    12. }
     
  9. Jeff-Kesselman

    Jeff-Kesselman

    Joined:
    Apr 5, 2010
    Posts:
    99
    Hmm this seems over complicated to me. For my app i have a monitor object that can either check just my transform, or every transform that effects me (parents too) and call me back if it changed. It even works if the parenting is changed at run time. Its not very big...


    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Events;
    6.  
    7. public class TransformChangeWatcher : MonoBehaviour
    8. {
    9.     [Serializable]
    10.     public class ChangeListeners : UnityEvent<Transform>
    11.     {
    12.     }
    13.    
    14.     public ChangeListeners changeListeners = new ChangeListeners();
    15.     public bool isRecursive = false;
    16.  
    17.  
    18.     private Transform myXform;
    19.     // Start is called before the first frame update
    20.     void Start()
    21.     {
    22.         myXform = GetComponent<Transform>();
    23.     }
    24.  
    25.     // Update is called once per frame
    26.     void Update()
    27.     {
    28.         bool changed = false;
    29.         if (isRecursive)
    30.         {
    31.             //the line below is not cached in case parenting is dynamic
    32.             Transform[] xformArray = GetComponentsInParent<Transform>();
    33.             foreach (Transform xform in xformArray)
    34.             {
    35.                 changed = changed | xform.hasChanged;
    36.                 //reset bit
    37.                 xform.hasChanged = false;
    38.             }    
    39.         }
    40.         else
    41.         {
    42.             changed = myXform.hasChanged;
    43.             myXform.hasChanged = false;
    44.         }
    45.  
    46.         if (changed)
    47.         {
    48.             changeListeners.Invoke(myXform);
    49.         }
    50.        
    51.     }
    52. }
    53.