Feature requests for quality of life

leanon00

Member
-Node breakpoint?

-Conditional: Invert condition flag (success -> failure & failure -> success)
-Stacked Conditional Decorator

-End(TaskStatus) function => when you wait for a callback from an async system and you dont want to keep a status in every task and override update

example:
public sealed class PulseRangeSensorAsync : AAgentAction
{
...
public TaskStatus taskStatus;

public override void OnStart()
{
taskStatus = TaskStatus.Running;
bool startedPulse = agentBrain.rangeSensor.TryStartPulseAsync(rangeSensorConfig, handleRangeSensorPulsed);

if (!startedPulse)
taskStatus = TaskStatus.Failure;
}

public override TaskStatus OnUpdate() => taskStatus;

private void HandleRangeSensorPulsed(RangeSensor sender, GameObject detectedPlayerGameObj) => taskStatus = TaskStatus.Success;
}
-------------------
 

Attachments

  • 1760025551001.png
    1760025551001.png
    3.5 KB · Views: 6
  • 1760025678465.png
    1760025678465.png
    9.9 KB · Views: 5
Last edited:
Graph editor:
- graph refactor => when changing a task type name have the option to find and replace the nodes that contain the old name and automatically fix it with a chosen name
- type dropdown for the find option (will auto complete the string value so it will also keep the functionality from now)
View attachment 14941

Context menu on nodes:
-copy paste node assigned tasks (stackable)
-combine stacked nodes when multi selection (when selected same type)
-option to export as subtree when selecting a node (from that node -> all its branches)

Game objects Hierarchy:
Context menu (optional => activable from the preferences settings)
Button:
=> left click=> open the first found graph
=> right click=> open context menu from where we can access a specified tree/subtree (or even deeper ) from that game object hierarchy
View attachment 14942
-------------
Blackboard:
- when assigning a shared variable for a task have an option to create a new empty shared variable with a default name and default value in a specific blackboard (graph, game object etc)
 
Last edited:
Small built-in assertion/debug helper system (or exposing a few internal properties) to make it easier to catch setup mistakes in Behavior Trees during development.

While working on my own project (with the previous BT system), I made a little extension that really helped me find issues like missing references or unassigned blackboard parameters. It’s editor-only (wrapped with [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]), so it doesn’t affect runtime builds, but it makes debugging much smoother.

example:

public SharedVariable<GameObject> targetVariable;

task.Ext_DebugAssertNotNull(targetVariable);
task.Ext_DebugAssertParamDefined(targetVariable); // checks if shared variable was assigned a value from the blackboard
task.Ext_DebugAssert(condition, "Some condition failed");
task.Ext_DebugWarning("Something looks off");


Each log or assertion includes the task name, ID, game object, and behavior tree name — so I can instantly tell which task failed and where.
 
My previous extensions:

public static class TaskDebug
{
[Conditional(BuildDefines.kUnityEditor),
Conditional(BuildDefines.kUnityDevelopment)]
public static void Ext_DebugAssertNotNull(this Task task, object obj, [CallerArgumentExpression("obj")] string paramName = "", [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = 0)
{
bool objectIsNull = obj.Ext_IsUnityRefNull();
if (!objectIsNull) // BBParams can't be null!
{
if (typeof(BBParameter).IsAssignableFrom(obj.GetType()))
{
var param = (obj as BBParameter);
string message = $"Null reference detected on param [{paramName}]!";
if (param.useBlackboard)
Ext_DebugAssert(task, param.isDefined && param.value != null, message, callerName);
else
Ext_DebugAssert(task, param.value != null, message, callerName);

return;
}
}

Ext_DebugAssert(task, !objectIsNull, $"Null reference detected on [{paramName}]!", callerName);
}

[Conditional(BuildDefines.kUnityEditor),
Conditional(BuildDefines.kUnityDevelopment)]
public static void Ext_DebugAssertParamDefined(this Task task, BBParameter param, [CallerArgumentExpression("param")] string paramName = "", [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = 0)
{
if (param.isDefined)
return;

Ext_DebugAssert(task, param.isDefined, $"BBParam [{paramName}] not defined!", callerName);
}

[Conditional(BuildDefines.kUnityEditor),
Conditional(BuildDefines.kUnityDevelopment)]
public static void Ext_DebugAssert(this Task task, bool condition, object assertFailMessage, [CallerMemberName] string callerName = "",
[CallerLineNumber] int lineNumber = 0)
{
if (condition)
return;

UnityEngine.Debug.Assert(condition, $"{assertFailMessage} " + Ext_GetTaskDebugInfo(task, callerName, lineNumber),
task.ownerSystem.agent);
}

[Conditional(BuildDefines.kUnityEditor),
Conditional(BuildDefines.kUnityDevelopment)]
public static void Ext_DebugLog(this Task task, object msg, [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = 0)
{
UnityEngine.Debug.Log($"{msg} " + Ext_GetTaskDebugInfo(task, callerName, lineNumber),
task.ownerSystem.agent);
}

[Conditional(BuildDefines.kUnityEditor),
Conditional(BuildDefines.kUnityDevelopment)]
public static void Ext_DebugWarning(this Task task, object msg, [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = 0)
{
UnityEngine.Debug.LogWarning($"{msg} " + Ext_GetTaskDebugInfo(task, callerName, lineNumber),
task.ownerSystem.agent);
}

public static string Ext_GetTaskDebugInfo(this Task task, [CallerMemberName] string callerName = "", [CallerLineNumber] int lineNumber = 0)
{
return $" at line: {lineNumber} \non: Task: {task.name} || Id: {task.ID} || Caller: {callerName} || GameObj: {task.ownerSystem.agent.name} || " +
$"ObjRoot: {task.ownerSystem.agent.transform.root.name} || BehTree: {task.ownerSystem.contextObject.name}";
}}
 
and the helper for null checks:

/// <summary>
/// Checks custom C# types that are Unity objects to make sure if the underlying Unity object is actually null or not. (ex: reference to an interface)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Ext_IsUnityRefNull<T>(this T obj) where T : class
=> obj == null || (obj is UnityEngine.Object unityObj) && unityObj == null;
 
Option to Disable Automatic Project View Selection on SubTree Double-Click

Would it be possible to add an option to disable the Project View selection that happens when double-clicking a SubTree reference? It can get a bit annoying when working between multiple trees, scripts, and config assets — I’d prefer to control when it changes focus without locking the Project View.

Maybe the SubTree could be opened from a context menu or a small button on the node as an alternative?

Also noticed that if a node has multiple SubTree references, it always opens and selects the last one — a quick menu to choose which one to open would be super helpful.
 
Would it be possible to add an option to disable the Project View selection that happens when double-clicking a SubTree reference? It can get a bit annoying when working between multiple trees, scripts, and config assets — I’d prefer to control when it changes focus without locking the Project View.
The subtree opens because the ScriptableObject is selected within the project. So you want to have the subtree to open without it being selected? This sounds sort of similar to this: https://www.opsive.com/forum/index.php?threads/selection-change-improvement.12179/
 
Back
Top