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

Serialization Prefab Bug?

Discussion in 'Scripting' started by Desprez, Jun 24, 2015.

  1. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I have a script that instantiates a prefab referenced by dragging it into the inspector.

    If I change a value on the prefab I'm now getting a strange null reference in a script that is a component of that prefab. This will persist between playbacks even if I change the value back, until I either:
    1) Re-drag the reference
    2) Play the scene once without that prefab getting instantiated

    I've never had a problem with changing prefab values like this before.
    (Note, that I'm not changing these values during runtime.)

    What might be causing this?
     
    Last edited: Jun 24, 2015
  2. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    are you dragging the prefab from the project list or the hierarchy list?
     
  3. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Project list.
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Could you post the relevant code?
     
  5. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    The exact place in the code triggering the error is pretty innocuous. Whereas the relevant code is probably around 500 lines...
    Here's the function where it occurs, Line 41
    Code (JavaScript):
    1. private function DoDetector () {
    2.     iteratingDict = true;
    3.     var _detectTime : float = Time.time; // store current time so multi-frame detect passes have the same time
    4.     var _targets : GameObject[];
    5.  
    6.     // generate initial target list      
    7.     if ( factionMethod == FactionMethodType.Tags ) {
    8.         _targets = new GameObject[0];
    9.         for (var f:int=0; f < targetableFactions.Length; f++) { // for each targetable faction
    10.             var _oneFaction = GameObject.FindGameObjectsWithTag( targetableFactions[f].ToString() ); // find all targets with this targetable tag
    11.             _targets += _oneFaction;
    12.             yield;
    13.         }
    14.         for (var t:int=0; t < _targets.Length; t++) {
    15.             if ( (transform.position - _targets[t].transform.position).sqrMagnitude > detectorRange * detectorRange ) {
    16.                 _targets[t] = null; // out of range
    17.             }
    18.         }
    19.         yield;
    20.     }
    21.  
    22.     if ( factionMethod == FactionMethodType.Layers ) {
    23.         var _colliders = Physics.OverlapSphere( transform.position, detectorRange, mask );
    24.         _targets = new GameObject[ _colliders.Length ];
    25.         for (var c:int=0; c < _colliders.Length; c++) {
    26.             _targets[c] = _colliders[c].gameObject;
    27.         }
    28.         yield;
    29.     }
    30.  
    31.     detectedCount = 0; // **** temp
    32.     var _nextYield:int = loopYieldThreshold;
    33.     for (var d:int=0; d < _targets.Length; d++) {
    34.         if ( _targets[d] == gameObject ) { continue; } // skip self
    35.         if ( _targets[d] == null ) { continue; } // skip null
    36.  
    37.         var _statusScript:FS_Status = _targets[d].transform.root.GetComponent(FS_Status);
    38.      
    39.         // **** causes error when changing prefab
    40.         // **** error persists until re-dragging reference or playing scene once without that prefab
    41.         if (_statusScript.signature >= detectorResolution) { // can detector see this?
    42.         // **** error above
    43.  
    44.             // add to targetList
    45.             var _key:int = _targets[d].GetInstanceID();
    46.             if ( targetList.ContainsKey(_key) == false) { // don't try to overwrite exsisting key
    47.                 // new record
    48.                 targetList.Add( _key, new TargetData( _targets[d].transform, _statusScript, 0, 0, 0, 0, 0 ) );
    49.             }
    50.             // update record
    51.             targetList[_key].lastDetected = _detectTime;
    52.             targetList[_key].sqrMagnitude = (transform.position - _targets[d].transform.position).sqrMagnitude;
    53.             // other data gathered by detector
    54.             detectedCount++; // **** temp
    55.         }
    56.         if ( d >= _nextYield ) {
    57.             _nextYield += loopYieldThreshold;
    58.             yield;
    59.         }
    60.     }
    61.     iteratingDict = false;
    62. }
     
  6. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I've continued to test this, and It's even more baffling now.

    I play the scene once, then stop it.
    I change the value of a variable in a prefab, then immediately change it back to exactly what it just was.
    Play the scene again, and get errors.

    How is this possible?

    Furthermore, it apparently also has something to do with the code in the first if block (dealing with tags)
    because this behavior doesn't occur with prefabs using the if block at line 22 (dealing with layers)
    (The initial _target array is either built using tags or layers)
    I've tried changing how the array is built, taken out the yield statements, taken out the setting of null, reduced it to one faction. I also tried restarting Unity. Nothing helps.

    Edit: Also tried re-creating the prefab. No luck there either.
    (I'm also fairly certain that not every instance of the prefabs in the scene are generating an error. Though I'm not sure which ones are or aren't causing a fit. There's 400 of them at the moment, and I'm not getting 400 errors at once.)
     
    Last edited: Jun 25, 2015
  7. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Here's the actual error:
    NullReferenceException: Object reference not set to an instance of an object
    FS_Scanner+$DoDetector$18+$.MoveNext () (at Assets/Scripts/FS_Scanner.js:164)

    line 164 is referring to line 41 in the code above.
     
  8. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Conversely, if I put the prefabs directly into the scene before runtime, there are no errors even if I change them between runs.
    However, if I change the prefabs in the project list that they reference, then errors will occur.

    Additionally, the error isn't being generated from the changed prefabs' scripts. It's the unchanged prefabs that are trying to reference the changed objects, throwing the error.

    But oddly enough, if I then change anything in the scene, even an unrelated object like a light, the error will magically vanish. At least until the error-causing prefabs get changed again.
    Edit: Even just changing the listed order of the objects in the hierarchy view prevents the error.

    This seems to suggest it's something to do with the linking of a prefab to the scene objects. This linking information is then fixed when the scene cache?? tree?? is forced to rebuild due to something changing.

    Aha! Saving the scene immediately after a prefab change also prevents the error.
    Edit: Whoops, now saving isn't helping. Ugg.
    Edit: Creating a new scene from scratch didn't help either.

    Any suggestions?
     
    Last edited: Jun 25, 2015
  9. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Okay, sorry about the multiple-posts, but I've now discovered that simply clicking on the hierarchy panel after a prefab change will prevent the error.

    So it's GOT to at least partly be a bug in Unity, right?
    It still only affects the objects that use the 'tags' part of the code, so something in there must be causing it too.
     
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    That's a pretty big clue right there.

    When you're clicking the hireachy panel, are you clicking the object with this script on it?

    What's probably happening is that by clicking, you're making Unity redraw the inspector. As a part of drawing the inspector for the object, Unity makes the object serialize itself.

    Prefabs are object "streams" - a set of data about how to build the prefab, and a set of data atop of that containing the local changes to that prefab. I'm pretty sure that as Unity serializes and deserializes a scene, it bakes in the data from the prefab stream so you can play it.

    This means that by clicking the hireachy panel (and probably the scene view or anything else that makes Unity update the scene) makes Unity notice that the prefab has changed, which makes it write those changes to the scene. If you just change the prefab and press "play", on the other hand, a it seems like parts of those changes are not written properly - possibly just the tags, if that's what's giving you a headache.
     
  11. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    I'm guessing _statusScript somehow goes missing for some reason.
    Have you tried logging the objects in question to try and find where the missing link is exactly?
    Code (csharp):
    1.  
    2. var _statusScript:FS_Status = _targets[d].transform.root.GetComponent(FS_Status);
    3. if (!_statusScript)
    4. {
    5.     Debug.Log("Target", _targets[d]);
    6.     Debug.Log("Root", _targets[d].transform.root);
    7.     Debug.Break();
    8.     yield;
    9. }
    10.  
    Here's some other questions worth asking:
    Is your prefab modified by custom editors in any way?
    Is the prefab modified without being instantiated in the scene?
    Does the prefab link to itself somehow?
     
  12. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Debug.Log finds the target object just fine, and the root as well. (though in this case they are the same object)
    GetComponent comes up null, and the script is most certainly there.

    No custom editors.
    Both yes and no. I've tried it both ways, and both generate the error.
    No.
     
    Last edited: Jun 25, 2015
  13. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    @Baste
    I don't even have to click an object. Just making the hierarchy panel active is enough.

    Ohh, new info. It's not the clicking of the panel after all - it's the clicking off of any object prefab that has the FS_Status script. It just so happens that clicking the scene or hierarchy also do that.

    There's nothing unusual about FS_Status. It's pretty basic.
    Code (JavaScript):
    1. #pragma strict
    2.  
    3. var health : float = 10;
    4. var signature : float = 10;
    5.  
    6. function Start () {}
    7.  
    8. function Update () {
    9.     if ( health <= 0 ) {
    10.         Destroy(gameObject);
    11.     }
    12. }
    Edit:
    Okay, even clicking on unchanged prefabs with FS_Status don't clear the error. However, I just made a new prefab and attached FS_Status, and clicking that one WILL clear the error.
    Also just tried re-creating the problem object from scratch - it didn't help.
     
    Last edited: Jun 25, 2015
  14. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Did some more digging.
    I reduced this test to two objects scanning each other. These objects are already present present in the scene when hitting play. Each is an instance of a seperate prefab. And I'm using Debug.Log to trace exactly what each is detecting.

    The expected result is that I'll see each object build a target list of 1 object (the only other target in the scene).

    This is exactly what happens.

    But when I change the prefab in the project list, then something else happens.

    The changed prefab object will detect the other as it should.
    However, the unchanged prefab object will now detect TWO of the changed prefab targets and one is listed as a clone. This clone is not visible in the hierarchy panel. It seems to have all the same components as the original, except the scripts. (I found this out by returning the number of components, and it's number is that minus the number of script components. And that number only changes when removing non-script components)

    So the way around this seems to be to test for the script component, and if it's false, then set that array entry to null.

    It's a bit hacky, but them main issue I have with it is that GetComponent is slow, and now I have to do it twice per target. I'm not real happy with this.

    Any other ideas for dealing with this?
     
    Last edited: Jun 27, 2015
  15. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Could you post the trimmed down-version? I'd like to see this.
     
  16. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    You mean the project, exported as a package?
    I'll see if I can put something together.

    Edit: Hmm. It turns out setting the entry to null, isn't actually working, which is very odd. I can still avoid the error by skipping around it, but the entry in the array is still there.

    I have a clean-up routine that checks for null entries, and the phantom object is effectively avoiding clean up because _targets[d] = null isn't working. Hrumph.

    Ok, try this. Not sure if I need to zip it or anything.
    mobfarmgames.weebly.com/uploads/5/4/4/2/54422083/testscan.unitypackage
     
    Last edited: Jun 28, 2015
  17. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Starting to look like an regression, this looks very similar.

    Not gotten time to look at your project yet.
     
  18. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Turns out I can destroy the object so it only gets detected once, however that causes errors when exiting play.
    But setting to null seems to be working after all, not sure how it's different now. It's possible I was setting the wrong thing to null. Dunno.

    I'm starting to think that FindGameObjectsWithTag is collecting old serialized versions of objects that it shouldn't. Whether or not this is FindGameObjectsWithTag fault or a fault in some kind of deserialization routine, I couldn't say.
    They way I understand it, tags basically creates an array of similarly tagged objects, so it's possible that the array isn't being cleaned up properly when changing a prefab.
     
    Last edited: Jun 30, 2015
  19. Yarbius

    Yarbius

    Joined:
    Apr 29, 2014
    Posts:
    22
    I am having a similar issue (Thanks for pointing me to this post). Based on your notes above I believe I am having exactly the same issue. If you so much as click on a tagged prefab then you cannot build a collection of tagged prefabs in code when executing the project, instead getting a Null Reference Error as soon as you try to iterate the collection. Clicking on any item in the Hierarchy before execution makes everything work as expected. Have you posted a bug report, if not we should do so as this is clearly a Unity issue.
     
  20. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    You're correct about simply having to click on a tagged prefab. I had assumed it had to be changed and present in the scene.
    I'm also noticing that the prefab doesn't even have to have an instance in the scene.

    I was about to submit a bug report, but I'm having a little trouble recreating this in a simple environment. Something else seems to be at play here as well.
     
  21. Yarbius

    Yarbius

    Joined:
    Apr 29, 2014
    Posts:
    22
    Yeah I made a simple project to try and recreate the issue but could not.

    I did realize that an issue I had a few weeks ago was probably the same thing. I had random seeming instances where I could not create a reference to a UI canvas in code but, in that case, I simply changed the reference to a public variable and set it in the inspector.
     
  22. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Well this begs the question then, about what specific thing in our scripts is causing unity generate a tag error.
     
  23. Yarbius

    Yarbius

    Joined:
    Apr 29, 2014
    Posts:
    22
    That is definitely the question. I was able to make progress working tonight without errors knowing that I just need to click the hierarchy before execution. I have no idea what else to do to fix it though. My programming is mostly in Windows Forms and WPF with this being my first Unity project.