Possible unintended serialization

gumboots

Member
Hi there!

I've been doing a bit of work with ExternalBehaviors (I had a thread about it in another forum). And I've encountered some errors that seem to completely vanish with a restart of Unity. A theory of mine is that you're using ScriptableObjects. One behaviour of these (that I discovered the hard way) is that they behave differently to MonoBehaviors when it comes to private and/or static fields.

While not stored between Unity loads, they do retain values between playing/stopping playing. The only way to prevent this is to mark them explicitly as [NonSerialized].

It might not have any effect on the asset, feel free to delete this! But I thought I'd flag it just in case.
 
ScriptableObjects are project level assets rather than scene level assets so you're right in that they are serialized a bit differently. I am going back and forth on whether it is the correct workflow to prevent scriptable object serialization while Unity is playing. On hand it does make sense, but on the other it's not the Unity way of doing things so to people who use to a certain workflow it'll mess them up.
 
I believe the way they serialize is fine, useful even. If you're working with project level assets, I think it's fairly common to want changes to persist, as they're usually some sort of data stores, right?

However I don't believe that storing the value of private fields is correct. To clarify: The value isn't actually being serialized. It just isn't cleared between plays. If you quit the Editor and open it again, the values are wiped. A real-world example:

Lets say you have a Dictionary populated when the game begins on a ScriptableObject. You store the Dictionary in a private field, _dictionary. You access it with a public property, Dictionary, which checks if _dictionary is null, if it is, it creates and populates it. The issue is, inbetween plays, _dictionary isn't nulled. So new values won't be added, and you'll search for a long time for a bug that doesn't actually exist. And that's just one example, I've had this trip me up a few times, hehe.
 
Incidentally, occasionally with my BehaviorTreeReference tasks (of which I've made my own runtime variant):

C#:
public class BehaviorTreeReferenceRT : BehaviorTreeReference {
    public SharedNPCController NPCController;
    public NPCBehavior NPCBehavior;

    public override ExternalBehavior[] GetExternalBehaviors() {
        return new []{ NPCController.Value.GetBehavior(NPCBehavior) };
    }
}

I get a "Not set to an instance of an object" error on the return new line. However restarting Unity seems to fix it up. My version control doesn't show any changes, so I can only assume there's something, somewhere being stored by Unity that doesn't update a SharedVariable to a new value when it should? (Obviously this is pretty specific to my project, I doubt you could debug this, hehe.)
 
Is there a particular private field that you are seeing have a populated value persist? Is it on the task or the tree?

When the external tree is reference it shouldn't be actually changed at all, instead when the tree is loaded the behavior tree loads a copy of that external tree so it can then do its own modifications.
 
Nothing in particular, it's just the ability to restart Unity and solve some bugs I'm seeing that points to it. But I have no doubt it's because of my [MappedProperty] attribute, and something happening in there. Prior to getting the BehaviorSource and iterating the SharedVariables, I set the value of NPCController with behaviorTree.SetVariableValue("NPCController", npcController). The NPCController variable is the value reporting null, even though I can see it assigned in the inspector.
 
So it definitely seems like it has to do with SetVariableValue. If I run this and then play I get an `NullReferenceException: Object reference not set to an instance of an object` error. Restarting Unity and the error is gone. So I'm not entirely sure what's happening, but it seems that SetVariableValue at edit-time isn't doing something I'd like it to?
 
Hmm, this one is going to be hard for me to debug. What is the full stack trace of the error? Have you imported the runtime source to see if you can track down what is null?
 
It is my non-mapped property that is null, which is a component on my prefab. Here is the full stack trace: https://pastebin.com/w55gjX6x The Task it errors on is this:
C#:
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

namespace MyGame.Gameplay.AI.BehaviorTree.Actions {
    [TaskIcon("BehaviorTreeReferenceIcon.png")]
    public class BehaviorTreeReferenceRT : BehaviorTreeReference {
        public SharedNPCController NPCController;
        public NPCBehavior NPCBehavior;

        public override ExternalBehavior[] GetExternalBehaviors() {
            return new []{ NPCController.Value.GetBehavior(NPCBehavior) };
        }
    }
}

The line is the return new[], and the null is NPCController. When I run my automation, I call behaviorTree.SetVariableValue("NPCController", npcController) from within an Editor script. npcController is definitely not null, as it's the Editor script's target cast to NPCController. This seems to be what is causing the issue, but if I restart Unity the error goes away.
 
And this is the only part of your automation script that isn't working? If you place the Unity inspector in debug mode and look at the serialization data for the external tree is the reference to NPCBehavior filled in?

If NPCBehavior is a Unity Object then it will be in the Unity Objects array, and an index to that element in the array will be listed within the serialization data. I would switch to JSON serialization just to be able to see the data better.
 
Hi @Justin, apologies for the delay! Yep, this is the only part that doesn't work. And restarting Unity actually fixes the issue (it's just a bit time consuming).

To my untrained eye it looks like the references are correct? (It's the first property, so it would be the first element in the array, right?) Here's the (prefab) in debug mode after running my script:

15681569

If those aren't the right places I should be looking let me know!

Running the game like this results in the error I posted previously. But restarting Unity, it works as expected, with any newly created properties.
 
Those values look correct. Can you switch to JSON serialization to see if that helps at all? You can do this from the Behavior Designer preferences on the top right of the editor.
 
Hi Justin, apologies for the delay. Switching to JSON the console is spammed with the following error, even if the Behavior Designer Window is simply open, without any behavior selected:
Code:
String too long for TextMeshGenerator. Cutting off characters.
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

Unfortunately I'm also still seeing the same error with JSON serialization.
 
Top