Behavior Graph Placeholder nodes and Source Control

This is a dire warning to anyone that uses source control and the behavior package together and a plea to the team to look into this. Let me explain what I mean with what I just experienced.

I’m currently working on a small project with only a few people on the team. We use git for source control as is pretty standard. I’ve spent the last few days working on pulling in the Behavior 1.0.6 package and putting together a behavior tree for some NPC agents in our project. This includes custom actions and conditions. After nearing the end of this bit of work, I stashed my changes in git, pulled latest, and went about fixing conflicts to get my stuff ready for putting up for a PR. Conflicts weren’t too bad, none of it was related to the Behavior package as it hadn’t been pulled in before my stashed work but I did have some compiler errors to fix from the latest changes that came in with our project work. I went about fixing the script errors, fixed some prefab things that needed redone, some scene fixes, you know, standard stuff. As I was testing the behavior agent before pulling latest, I still had the behavior graph window open to that graph but didn’t pay it any attention at first.

Once I had all of the compiler errors fixed, I went to tweak something in the behavior graph only to now notice that I had a red warning that there were “# Placeholder nodes” and I could swap between them to inspect them. Every one of my custom actions had been replaced with a Placeholder node (I’m assuming because the compiler errors prevented the custom action classes from compiling). There was no way to reconnect the (now compiling) actions to replace the Placeholder nodes. However, that was only the beginning of the problem, and the only one that it actually warned me of.

In total, I found out the following:

  • All custom actions I had written had been replaced by Placeholder nodes and written into the .asset file as Placeholder nodes
  • All custom conditions that I had written were gone entirely
  • All blackboard variables that were based on custom classes were also gone

It had saved all of these changes out to the .asset file of the behavior graph with no warning or confirmation that it would do so and overwrite large swaths of work. The actions weren’t as bad as it at least tried to maintain the name data of the custom actions so I could have remade most of them based on that. The conditions disappearing with no indication of what was there is unrecoverable. My graph wasn’t very large and there were parts that I could not easily recall what those custom conditions would have been set to. I can only imagine the nightmare this would wreak on a far more developed behavior graph that has had months of additions and tweaks potentially wiped out simply from day to day development when using source control.

To the team, I can think of two ways to resolve it:

  • Never save Placeholder node information into the asset file, allow it to fail and force the user to fix the issue themselves. Also never strip conditional and variable information if its not understood (IE, isn’t compiled yet). Treat it like editing a prefab in isolation where you are stopped from saving if there are invalid monobehaviours detected.
  • If you must save Placeholder node information, detect that, and before saving, warn and confirm with the user before allowing the .asset to be blindly saved.

I do like the package. It’s got plenty out of the box to get most people started pretty well into behavior trees. It’s also relatively easy to extend, but could definitely use more documentation on its existing components and more than just access to the scripting API for more engineer focused people looking to extend and use more of its capabilities. Yes I can (and did) spend hours digging through the source files figuring the system out (including adding temp logs when trying to sort out issues), but it would be much easier and more time efficient to have that be in the documentation. I’ve found some other minor gripes along the way, but most of those I can work around to varying degrees of success.

This issue, however, makes me incredibly nervous to use this package anywhere near a larger team or a production environment if tons of data can be wiped out with little to no warning.

For those worrying, I was able to restore a stashed version of the .asset file in question without all of the removed/Placeholder node information so I was able to fix my instance but it will likely happen again in the future. I hope you guys are able to look into the issue and figure out a good solution that doesn’t lead to losing a lot of work with custom actions/conditions.

Hi @zkrizo

Thank you for your report, this is indeed very concerning and we will look at this issue ASAP.

The id metadata from the NodeDescriptionAttribute should prevent such dramatic issue. Something unexpected must be happening but it’s hard to pinpoint from the thread only. We would need additional details to be able to efficiently handle this issue. Could you open a ticket for it?

You have provided plenty of valuable information and good suggestion about how we could improve the asset saving mechanism. I’ll make sure to bring them forward.

Thank you for your time and sorry for the inconvenience :bowing_man:

2 Likes

Hi @zkrizo ,

Thank you so much for this feedback. I’m glad you were able to revert the asset and recover your work!

I’ll look into improving the Placeholder to prevent exactly what you described from happening. Would you like to make a public ticket so you can track it? If you do, link it here and I’ll prioritize it.

Thanks again and sorry for the issue!!!

Can you send more information about other gripes? Write a wishlist of what can make Behavior better, if you are happy to do so :slight_smile:

I’ve filed a bug through the Report Bug in Unity’s Editor for this one (IN-89897) but couldn’t find a way to make one on the public issue tracker but if there is a way and you can point me at it, I can make one there too.

As for other gripes/issues. Some are minor, some I tried to figure out by extensive logging in the components that were having issues.

Minor:

  • Several Actions relating to Conditions have a Truncate Node option if you don’t want to view all of the conditions right in the behavior tree and it will truncate it to “If [all/any] of [#] Conditions are true”. This is the case for Flow/Conditional Branch, Flow/Repeat (Condition), Flow/Abort/Restart, and Flow/Abort/Abort. This is NOT the case for Actions/Conditional Guard. Conditional Guard will force truncation if more than 1 condition exists on it rather than giving the Truncate Node option
  • Conditional Guard is under Actions/Conditions rather than Flow/Conditions with the rest of the things that control the flow of the behavior graph including the other conditional action Conditional Branch
  • Custom Lists for blackboard variables (which there was a previous forum post on and I know you guys are working on it :+1: but putting my voice here in support for it as well)
  • Documentation on proper ways to use things like setting blackboard variables, especially for blackboards that aren’t tied to an agent (IE, shared world data that all agents need to know about)
  • Using a RuntimeBlackboardAsset in a [SerializeField] on a monobehavior will cause data changed during gameplay to be saved back to the object if you don’t Instantiate a copy from it and use that instead (which makes sense once you realize that RuntimeBlackboardAsset is just a scriptable object) but again, no documentation on that outside of the script API
  • Navigation actions do not read the variable information set on the NavmeshAgent in the prefab (IE, Speed) but instead rely on hidden data set in the node itself that isn’t broadcast through the Node’s story. Since that component already handles things if a NavmeshAgent isn’t there, I would expect that if it WAS there, that the data on the NavmeshAgent would be used instead (or at least an option to override and then restore the original back when the node finished)
  • No easy way to open up a subgraph that is pointed to by a RunSubgraph node (Double click, right click, etc). Have to go find it in the hierarchy and open it manually. Would be a nice QoL thing

Bugs (other than the major one above, these take some explaining)

Global shared blackboard variables and OnVariableChanged event for conditionals:
I’ve got a manager that is keeping track of some world state boolean flag (IE, doorOpened). At the start of the game, this manager instantiates a copy of the RuntimeBlackboardAsset and sets any flags it needs to. It’s then responsible for setting those flags anytime anything changes during gameplay (IE, doorOpened). Agent behavior graphs then have a reference to this Blackboard in their blackboards. I had intended to use this to interrupt a path navigation if it changed (Restart (Variable Changed) → Conditional Guard (variable) → Navigate to target) to break out of the running Navigate to switch to other behavior. After some extensive logging, I was seeing a listener get properly added to the invocation list for OnVariableChanged in the VariableValueChangedCondition.OnStart. I saw no logs about it getting removed. Yet when the manager updated the variable and OnVariableChanged got called, the invocation list had only 1 entry rather than the 2 it had after the VariableValueChangedCondition added its listener and it was not receiving the event from the manager setting the variable. The SerializableGUID was the same for when the VariableValueChangedCondition added its listener and when the manager set the value. The variable in the blackboard is set to be shared so I don’t know why it wasn’t working. I eventually worked around it by not using a separate blackboard and having a shared variable on the agents blackboard itself for that flag, but I’d prefer to have it in one place and modifiable from there rather than relying on having an agent update it.

Run Subgraph OnValidate error:
I had some duplicate behavior that was used in several places so I decided to pull it out into a subtree and just run the subtree instead. Specifically, this was to navigate to an exit target and then use a custom action to call to an AI Manager to tell it to despawn that NPC. So, subtree needed the exit target and the agent reference (self). I set up the subgraph representation for this Exit subtree so I could set the variables appropriately (both self (gameobject) and exit point (gameobject)) and use the subgraph representation to see what the tree was rather than just “Run [graph]”. Worked great. Restarted Unity. 3 errors, all for the OnValidate method deep inside of the RunSubgraph class. That broke the viewer for the entire behavior graph with no way to fix it again. Had to revert the asset file again to fix it and eventually just didn’t make the subgraph story point to anything, just left it as text and assigned the variables in the node itself. I’ve been trying to get a simple repro project that I can send in for this one because I figured it would be easy, but so far it’s eluded reproduction in a clean environment (and now in my normal project so maybe that was a one-off but again, anything that can only be easily fixed by a revert and potential loss of work is not great. It’d be better if only that node had an indicator that it had a problem so you could fix or remove that node and not lose any other work done).

Custom Actions ending in “Action” get their file renamed:
If you create a new Action and name it somethingAction (IE “TestAction”) it will be prompted to save with an additional Action at the end (IE “TestActionAction.cs”). If you allow this, if you edit definition and change anything about it (IE, story text) and confirm, it will then rename the file to only have 1 Action (IE “TestActionAction.cs” → “TestAction.cs”). Works fine after that, but if its going to trim that extra Action then it shouldn’t prompt to save it with it in the first place. I can confirm this is also true for Sequence and Modifier. Condition will also save with two Conditions but I can’t find a way to edit it that gets it to rename itself so it may be fine stuck as just “SomethingConditionCondition” but for uniform behavior it should also probably not have a second Condition in the filename. I filed a bug for this one as its 100% reproable (IN-89905) really easily.

I think that’s all I can remember at the moment but if I think of any more, I can add them here or make a new post

Hi @zkrizo ,

Thank you so much for all the info!

I’ve opened a public bug for you and assigned it onto myself to try and fix asap :slight_smile: This link should be available in some time:

Let me answer each of your points as best as I can!

  • Several Actions relating to Conditions have a Truncate Node option if you don’t want to view all of the conditions right in the behavior tree and it will truncate it to “If [all/any] of [#] Conditions are true”. This is the case for Flow/Conditional Branch, Flow/Repeat (Condition), Flow/Abort/Restart, and Flow/Abort/Abort. This is NOT the case for Actions/Conditional Guard. Conditional Guard will force truncation if more than 1 condition exists on it rather than giving the Truncate Node option

We saw this feedback before and decided to enable the same behavior for Conditional Guard :slight_smile: Hopefully soon!

  • Conditional Guard is under Actions/Conditions rather than Flow/Conditions with the rest of the things that control the flow of the behavior graph including the other conditional action Conditional Branch

Yeah, that’s a weird one because they are technically an Action. Categories are a weird thing we tried to simplify from Behavior Trees. Won’t it be strange if we moved them to Flow while they’re an action node? I’m not sure what’s the right way.

  • Custom Lists for blackboard variables (which there was a previous forum post on and I know you guys are working on it :+1: but putting my voice here in support for it as well)

100%. It’ll come!

  • Documentation on proper ways to use things like setting blackboard variables, especially for blackboards that aren’t tied to an agent (IE, shared world data that all agents need to know about)

Got it! I’ll bring it up :slight_smile: Thank you!

  • Using a RuntimeBlackboardAsset in a [SerializeField] on a monobehavior will cause data changed during gameplay to be saved back to the object if you don’t Instantiate a copy from it and use that instead (which makes sense once you realize that RuntimeBlackboardAsset is just a scriptable object) but again, no documentation on that outside of the script API

Yes! You actually need to use the BlackboardReference instead, but I think it’s not quite ready. We had to get the BlackboardAsset ready for the release so we don’t break users post release when we add more support, but we didn’t get to fully finish the APIs and tools for sharing them, etc. I’m hopeful we can do that in Q1.

  • Navigation actions do not read the variable information set on the NavmeshAgent in the prefab (IE, Speed) but instead rely on hidden data set in the node itself that isn’t broadcast through the Node’s story. Since that component already handles things if a NavmeshAgent isn’t there, I would expect that if it WAS there, that the data on the NavmeshAgent would be used instead (or at least an option to override and then restore the original back when the node finished)

That’s a good point. We tried to put it all in the node, but a checkbox to not overwrite and then disable the Speed parameter perhaps should be the way to go. Or maybe if it’s set to 0 it’ll use default? Not sure! Probably a boolean.

  • No easy way to open up a subgraph that is pointed to by a RunSubgraph node (Double click, right click, etc). Have to go find it in the hierarchy and open it manually. Would be a nice QoL thing

It should be easy for us to add double click to open or right click to open. I’ll look into it :slight_smile: Thank you! I opened a public issue for you to follow:

Bugs

Global shared blackboard variables and OnVariableChanged event for conditionals

That’s odd! Can you provide some repro steps for this?

Run Subgraph OnValidate error

Oh boy, if you can get a stack to share or repro or anything that’d be great! Do you know what caused it?

Custom Actions ending in “Action” get their file renamed:

I think the best solution is for us to detect if you already called it Action/whatever and trim the extra. That or, not add it in the first place, but personally I found it really useful when I call my action “Navigate To” as opposed to “Navigate To Action” and my NavigateTo file is called NavigateToAction.cs. What do you think

I’m leaning towards just making sure there is no duplication there.

I also opened a ticket for this: Unity Issue Tracker - Creating an Action node which name ends with Action creates ActionAction.cs file

Thanks again for the great feedback! :slight_smile:

2 Likes

Hi @zkrizo ,

Just FYI I’ve started working on a fix and I’m making good progress:

Hopefully soon!

2 Likes

Hi @ShaneeNishry,
Thanks for the quick responses!

We saw this feedback before and decided to enable the same behavior for Conditional Guard :slight_smile: Hopefully soon!

Glad to hear I’m not the only one who noticed this and it’ll have the same behavior soon :slight_smile:

Yeah, that’s a weird one because they are technically an Action. Categories are a weird thing we tried to simplify from Behavior Trees. Won’t it be strange if we moved them to Flow while they’re an action node? I’m not sure what’s the right way.

My point is whether Conditional Guard should be an Action or whether its more suited conceptually to be a Composite. It’s description says “Allows flow to pass only if the specified condition(s) are met.” so its referred to as being purpose built to control flow. The Composite class summary is “Composite nodes serves as a control structure that manages the flow and organization of other nodes within the tree.”. To me, that lines up that the Conditional Guard should be a Composite like the Conditional Branch is but without the True/False output Nodes that Conditional Branch is (since Conditional Guard only allows flow passage if the conditionals return true). Essentially Conditional Guard is a Conditional Branch that just has an empty False output node. In fact, you can duplicate the exact behavior with Conditional Branch and the only downside is that if you leave an output Node empty in Conditional Branch it will log a warning in editor only that says “Branching condition evaluated as false, but there is no child to run. Ignore this warning if this is intentional.”.

I initially was implementing guards with the Conditional Branch because I didn’t notice that there was the Conditional Guard under Actions instead of Flow. If anything, maybe update that False output node log to point out that there is a Conditional Guard action which may be more suited to what they are doing if they’re not using the False branch of Conditional Branch?

It will probably eventually fall under what the preference is for how the tree should be built using it. Is the expectation that it should be used as part of a sequence as a leaf node with success passing to the next node because of the sequence node above:


Or that it should be used with the successive node being connected directly to it?

I know either way works currently so its a matter of preference. Maybe that’s just my weird quirk that has always bugged me about classic behavior tree architecture that these kinds of conditional guards are treated as the first leaf nodes in a sequence approach rather than a more fall-through approach.

That’s a good point. We tried to put it all in the node, but a checkbox to not overwrite and then disable the Speed parameter perhaps should be the way to go. Or maybe if it’s set to 0 it’ll use default? Not sure! Probably a boolean.

I can definitely see the appeal of being able to override the speed that may be set on the NavmeshAgent for certain behaviors so I definitely support the option of something like an override in the behavior tree to change it.

That’s odd! Can you provide some repro steps for this?

When I get some time, I’ll see if I can get a mini project set up doing it. It’s entirely possible that I’m doing something in a way that wasn’t intended and that caused some weirdness but I’ll see if I can get it to happen again and get something to send your way.

Oh boy, if you can get a stack to share or repro or anything that’d be great! Do you know what caused it?

Unfortunately, this was late at night after a long day of other work so I didn’t think to grab the stack trace when it happened (and its long gone from my editor logs). I was trying to recreate it in a sterile project to send up to you guys but I couldn’t get it to replicate there and now I can’t get it to replicate in my original project either. I’ll keep trying but no clue if I can get it to repro again

I think the best solution is for us to detect if you already called it Action/whatever and trim the extra. That or, not add it in the first place, but personally I found it really useful when I call my action “Navigate To” as opposed to “Navigate To Action” and my NavigateTo file is called NavigateToAction.cs. What do you think

I would gravitate towards not adding the second one in the first place. It seems there’s already protections in place that allow a user to name things with Action (etc.) at the end of their Names that allow it to just work so no reason to back that out. Since there’s stuff in place to protect against it, I’d be fine leaving it up to user preference of if they want “Action” in the name or not. I’d just say fix the duplication on initial save personally as well.

Just FYI I’ve started working on a fix and I’m making good progress:

Excellent :smiley: ! Glad to see you already seem to have a good fix for the placeholder node part of it! Is the condition side of it also being looked at or is that going to be a tougher fix since there doesn’t seem to be a concept of PlaceholderCondition yet?

Hi @zkrizo ,

I’m just touching on this again because I’m worried I’ve misunderstood some of the communication here.

The proposed scenario is that:

  1. User names a node MyAction → Suggested filename is MyAction.cs (as opposed to MyActionAction.cs)
  2. User names a node AttackEnemy → Suggested filename is AttackEnemyAction.cs

The alternative scenario is that instead of 2. we have:

  1. Alternative: User names a node AttackEnemy → Suggested filename is AttackEnemy.cs

Personally I prefer the AttackEnemyAction.cs option. What do you think?

Thinking about it, we can also add a configuration in the Project Settings.

I’ll need to look into this because as you said, there is no such concept yet :confused:

I also prefer AttackEnemyAction.cs just to make it clear at a glance at the file what it is

So the following:
AttackEnemyAction → AttackEnemyAction.cs
AttackEnemy → AttackEnemyAction.cs

With consistency for the other Node types as well, IE Condition:
ValidPositionCondition → ValidPositionCondition.cs
ValidPosition → ValidPositionCondition.cs

Sequence:
SomeSpecialSequence → SomeSpecialSequence.cs
SomeSpecial → SomeSpecialSequence.cs

Modifier:
SomeSpecialModifier → SomeSpecialModifier.cs
SomeSpecial → SomeSpecialModifier.cs

1 Like

Perfect, then we’re aligned! This is what I’ve done :slight_smile:

(Video might still be processing) https://youtu.be/Mdt7CU5Hm-M