2026-06-06 20:12:40 +07:00
parent de84b2bf48
commit 97ac0f71f5
13682 changed files with 1125938 additions and 0 deletions
@@ -0,0 +1,327 @@
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.BehaviorTree
{
/// <summary>
/// Selector (OR) - Returns Success if ANY child succeeds
/// Tries children in order until one succeeds
/// </summary>
public class BTSelector : BTComposite
{
public BTSelector(string name = "Selector") : base(name) { }
public override BTStatus Tick()
{
for (int i = currentChildIndex; i < children.Count; i++)
{
var status = children[i].Tick();
switch (status)
{
case BTStatus.Success:
currentChildIndex = 0;
IsRunning = false;
return BTStatus.Success;
case BTStatus.Running:
currentChildIndex = i;
IsRunning = true;
return BTStatus.Running;
case BTStatus.Failure:
// Try next child
continue;
}
}
// All children failed
currentChildIndex = 0;
IsRunning = false;
return BTStatus.Failure;
}
}
/// <summary>
/// Sequence (AND) - Returns Success only if ALL children succeed
/// Executes children in order, stops on first failure
/// </summary>
public class BTSequence : BTComposite
{
public BTSequence(string name = "Sequence") : base(name) { }
public override BTStatus Tick()
{
for (int i = currentChildIndex; i < children.Count; i++)
{
var status = children[i].Tick();
switch (status)
{
case BTStatus.Success:
// Continue to next child
continue;
case BTStatus.Running:
currentChildIndex = i;
IsRunning = true;
return BTStatus.Running;
case BTStatus.Failure:
currentChildIndex = 0;
IsRunning = false;
return BTStatus.Failure;
}
}
// All children succeeded
currentChildIndex = 0;
IsRunning = false;
return BTStatus.Success;
}
}
/// <summary>
/// Parallel - Runs all children simultaneously
/// Returns based on policy (RequireAll or RequireOne)
/// </summary>
public class BTParallel : BTComposite
{
public enum Policy
{
RequireAll, // Success only if ALL succeed
RequireOne // Success if ANY succeeds
}
public Policy SuccessPolicy { get; set; } = Policy.RequireAll;
public Policy FailurePolicy { get; set; } = Policy.RequireOne;
private List<BTStatus> childStatuses = new List<BTStatus>();
public BTParallel(string name = "Parallel") : base(name) { }
public BTParallel(Policy successPolicy, Policy failurePolicy, string name = "Parallel") : base(name)
{
SuccessPolicy = successPolicy;
FailurePolicy = failurePolicy;
}
public override BTStatus Tick()
{
// Initialize status tracking
while (childStatuses.Count < children.Count)
{
childStatuses.Add(BTStatus.Running);
}
int successCount = 0;
int failureCount = 0;
int runningCount = 0;
for (int i = 0; i < children.Count; i++)
{
// Skip already completed children
if (childStatuses[i] != BTStatus.Running)
{
if (childStatuses[i] == BTStatus.Success) successCount++;
else failureCount++;
continue;
}
var status = children[i].Tick();
childStatuses[i] = status;
switch (status)
{
case BTStatus.Success:
successCount++;
break;
case BTStatus.Failure:
failureCount++;
break;
case BTStatus.Running:
runningCount++;
break;
}
}
// Check failure policy
if (FailurePolicy == Policy.RequireOne && failureCount > 0)
{
Reset();
return BTStatus.Failure;
}
if (FailurePolicy == Policy.RequireAll && failureCount == children.Count)
{
Reset();
return BTStatus.Failure;
}
// Check success policy
if (SuccessPolicy == Policy.RequireOne && successCount > 0)
{
Reset();
return BTStatus.Success;
}
if (SuccessPolicy == Policy.RequireAll && successCount == children.Count)
{
Reset();
return BTStatus.Success;
}
// Still running
if (runningCount > 0)
{
IsRunning = true;
return BTStatus.Running;
}
// Default to failure
Reset();
return BTStatus.Failure;
}
public override void Reset()
{
base.Reset();
childStatuses.Clear();
}
}
/// <summary>
/// Random Selector - Randomly picks a child to execute
/// </summary>
public class BTRandomSelector : BTComposite
{
private List<int> shuffledIndices = new List<int>();
private int currentIndex = 0;
public BTRandomSelector(string name = "RandomSelector") : base(name) { }
public override BTStatus Tick()
{
// Shuffle on first tick
if (shuffledIndices.Count == 0)
{
ShuffleChildren();
}
for (int i = currentIndex; i < shuffledIndices.Count; i++)
{
var childIndex = shuffledIndices[i];
var status = children[childIndex].Tick();
switch (status)
{
case BTStatus.Success:
Reset();
return BTStatus.Success;
case BTStatus.Running:
currentIndex = i;
IsRunning = true;
return BTStatus.Running;
case BTStatus.Failure:
continue;
}
}
Reset();
return BTStatus.Failure;
}
private void ShuffleChildren()
{
shuffledIndices.Clear();
for (int i = 0; i < children.Count; i++)
{
shuffledIndices.Add(i);
}
// Fisher-Yates shuffle
for (int i = shuffledIndices.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1);
var temp = shuffledIndices[i];
shuffledIndices[i] = shuffledIndices[j];
shuffledIndices[j] = temp;
}
}
public override void Reset()
{
base.Reset();
shuffledIndices.Clear();
currentIndex = 0;
}
}
/// <summary>
/// Random Sequence - Executes children in random order
/// </summary>
public class BTRandomSequence : BTComposite
{
private List<int> shuffledIndices = new List<int>();
private int currentIndex = 0;
public BTRandomSequence(string name = "RandomSequence") : base(name) { }
public override BTStatus Tick()
{
// Shuffle on first tick
if (shuffledIndices.Count == 0)
{
ShuffleChildren();
}
for (int i = currentIndex; i < shuffledIndices.Count; i++)
{
var childIndex = shuffledIndices[i];
var status = children[childIndex].Tick();
switch (status)
{
case BTStatus.Success:
continue;
case BTStatus.Running:
currentIndex = i;
IsRunning = true;
return BTStatus.Running;
case BTStatus.Failure:
Reset();
return BTStatus.Failure;
}
}
Reset();
return BTStatus.Success;
}
private void ShuffleChildren()
{
shuffledIndices.Clear();
for (int i = 0; i < children.Count; i++)
{
shuffledIndices.Add(i);
}
for (int i = shuffledIndices.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1);
var temp = shuffledIndices[i];
shuffledIndices[i] = shuffledIndices[j];
shuffledIndices[j] = temp;
}
}
public override void Reset()
{
base.Reset();
shuffledIndices.Clear();
currentIndex = 0;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 86de43f4d7cbe473fbd33222e6a6b693
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/BehaviorTree/BTComposites.cs
uploadId: 920982
@@ -0,0 +1,395 @@
using System;
using UnityEngine;
namespace SynapticPro.BehaviorTree
{
/// <summary>
/// Inverter - Inverts the result of child
/// Success -> Failure, Failure -> Success, Running -> Running
/// </summary>
public class BTInverter : BTDecorator
{
public BTInverter(string name = "Inverter") : base(name) { }
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
var status = child.Tick();
switch (status)
{
case BTStatus.Success:
return BTStatus.Failure;
case BTStatus.Failure:
return BTStatus.Success;
default:
IsRunning = true;
return BTStatus.Running;
}
}
}
/// <summary>
/// Succeeder - Always returns Success (ignores child result)
/// </summary>
public class BTSucceeder : BTDecorator
{
public BTSucceeder(string name = "Succeeder") : base(name) { }
public override BTStatus Tick()
{
if (child == null) return BTStatus.Success;
var status = child.Tick();
if (status == BTStatus.Running)
{
IsRunning = true;
return BTStatus.Running;
}
return BTStatus.Success;
}
}
/// <summary>
/// Failer - Always returns Failure (ignores child result)
/// </summary>
public class BTFailer : BTDecorator
{
public BTFailer(string name = "Failer") : base(name) { }
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
var status = child.Tick();
if (status == BTStatus.Running)
{
IsRunning = true;
return BTStatus.Running;
}
return BTStatus.Failure;
}
}
/// <summary>
/// Repeater - Repeats child execution N times or until condition
/// </summary>
public class BTRepeater : BTDecorator
{
public int RepeatCount { get; set; } = -1; // -1 = infinite
public bool RepeatUntilFail { get; set; } = false;
public bool RepeatUntilSuccess { get; set; } = false;
private int currentCount = 0;
public BTRepeater(int count = -1, string name = "Repeater") : base(name)
{
RepeatCount = count;
}
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
var status = child.Tick();
if (status == BTStatus.Running)
{
IsRunning = true;
return BTStatus.Running;
}
// Check termination conditions
if (RepeatUntilFail && status == BTStatus.Failure)
{
Reset();
return BTStatus.Success;
}
if (RepeatUntilSuccess && status == BTStatus.Success)
{
Reset();
return BTStatus.Success;
}
currentCount++;
// Check count limit
if (RepeatCount > 0 && currentCount >= RepeatCount)
{
Reset();
return status;
}
// Reset child and continue
child.Reset();
IsRunning = true;
return BTStatus.Running;
}
public override void Reset()
{
base.Reset();
currentCount = 0;
}
}
/// <summary>
/// Cooldown - Prevents child execution for a duration after completion
/// </summary>
public class BTCooldown : BTDecorator
{
public float CooldownTime { get; set; } = 1f;
private float lastExecutionTime = float.MinValue;
private bool childCompleted = false;
public BTCooldown(float cooldown, string name = "Cooldown") : base(name)
{
CooldownTime = cooldown;
}
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
// Check cooldown
float currentTime = Time.time;
if (currentTime - lastExecutionTime < CooldownTime)
{
return BTStatus.Failure;
}
var status = child.Tick();
if (status == BTStatus.Running)
{
IsRunning = true;
return BTStatus.Running;
}
// Child completed, start cooldown
lastExecutionTime = currentTime;
IsRunning = false;
return status;
}
public override void Reset()
{
base.Reset();
// Don't reset lastExecutionTime - cooldown persists
}
/// <summary>
/// Force reset cooldown
/// </summary>
public void ResetCooldown()
{
lastExecutionTime = float.MinValue;
}
}
/// <summary>
/// Timeout - Fails if child takes too long
/// </summary>
public class BTTimeout : BTDecorator
{
public float TimeoutDuration { get; set; } = 5f;
private float startTime;
private bool started = false;
public BTTimeout(float timeout, string name = "Timeout") : base(name)
{
TimeoutDuration = timeout;
}
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
if (!started)
{
startTime = Time.time;
started = true;
}
// Check timeout
if (Time.time - startTime >= TimeoutDuration)
{
child.Abort();
Reset();
return BTStatus.Failure;
}
var status = child.Tick();
if (status != BTStatus.Running)
{
Reset();
}
else
{
IsRunning = true;
}
return status;
}
public override void Reset()
{
base.Reset();
started = false;
}
}
/// <summary>
/// Condition Decorator - Only executes child if condition is true
/// </summary>
public class BTConditional : BTDecorator
{
private Func<BTContext, bool> condition;
public BTConditional(Func<BTContext, bool> condition, string name = "Conditional") : base(name)
{
this.condition = condition;
}
public override BTStatus Tick()
{
if (condition == null || !condition(Context))
{
return BTStatus.Failure;
}
if (child == null) return BTStatus.Success;
var status = child.Tick();
IsRunning = status == BTStatus.Running;
return status;
}
}
/// <summary>
/// Retry - Retries child on failure
/// </summary>
public class BTRetry : BTDecorator
{
public int MaxRetries { get; set; } = 3;
private int currentRetries = 0;
public BTRetry(int maxRetries = 3, string name = "Retry") : base(name)
{
MaxRetries = maxRetries;
}
public override BTStatus Tick()
{
if (child == null) return BTStatus.Failure;
var status = child.Tick();
switch (status)
{
case BTStatus.Success:
Reset();
return BTStatus.Success;
case BTStatus.Running:
IsRunning = true;
return BTStatus.Running;
case BTStatus.Failure:
currentRetries++;
if (currentRetries >= MaxRetries)
{
Reset();
return BTStatus.Failure;
}
child.Reset();
IsRunning = true;
return BTStatus.Running;
}
return BTStatus.Failure;
}
public override void Reset()
{
base.Reset();
currentRetries = 0;
}
}
/// <summary>
/// Delay - Waits before executing child
/// </summary>
public class BTDelay : BTDecorator
{
public float DelayTime { get; set; } = 1f;
private float startTime;
private bool waiting = false;
private bool delayComplete = false;
public BTDelay(float delay, string name = "Delay") : base(name)
{
DelayTime = delay;
}
public override BTStatus Tick()
{
if (!waiting && !delayComplete)
{
startTime = Time.time;
waiting = true;
}
if (waiting)
{
if (Time.time - startTime >= DelayTime)
{
waiting = false;
delayComplete = true;
}
else
{
IsRunning = true;
return BTStatus.Running;
}
}
if (child == null)
{
Reset();
return BTStatus.Success;
}
var status = child.Tick();
if (status != BTStatus.Running)
{
Reset();
}
else
{
IsRunning = true;
}
return status;
}
public override void Reset()
{
base.Reset();
waiting = false;
delayComplete = false;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 58ef143ab804043b2beb2a2ca8bfd85d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/BehaviorTree/BTDecorators.cs
uploadId: 920982
@@ -0,0 +1,356 @@
using System;
using UnityEngine;
namespace SynapticPro.BehaviorTree
{
/// <summary>
/// Action node - Executes a function
/// </summary>
public class BTAction : BTNode
{
private Func<BTContext, BTStatus> action;
private Action<BTContext> onStart;
private Action<BTContext> onEnd;
public BTAction(Func<BTContext, BTStatus> action, string name = "Action") : base(name)
{
this.action = action;
}
public BTAction(Action<BTContext> simpleAction, string name = "Action") : base(name)
{
this.action = (ctx) =>
{
simpleAction?.Invoke(ctx);
return BTStatus.Success;
};
}
/// <summary>
/// Set callback for when action starts
/// </summary>
public BTAction OnStart(Action<BTContext> callback)
{
onStart = callback;
return this;
}
/// <summary>
/// Set callback for when action ends
/// </summary>
public BTAction OnEnd(Action<BTContext> callback)
{
onEnd = callback;
return this;
}
public override BTStatus Tick()
{
if (action == null) return BTStatus.Failure;
if (!IsRunning)
{
onStart?.Invoke(Context);
}
var status = action(Context);
IsRunning = status == BTStatus.Running;
if (status != BTStatus.Running)
{
onEnd?.Invoke(Context);
}
return status;
}
public override void Abort()
{
base.Abort();
onEnd?.Invoke(Context);
}
}
/// <summary>
/// Condition node - Checks a condition
/// </summary>
public class BTCondition : BTNode
{
private Func<BTContext, bool> condition;
public BTCondition(Func<BTContext, bool> condition, string name = "Condition") : base(name)
{
this.condition = condition;
}
public override BTStatus Tick()
{
if (condition == null) return BTStatus.Failure;
return condition(Context) ? BTStatus.Success : BTStatus.Failure;
}
}
/// <summary>
/// Wait node - Waits for specified duration
/// </summary>
public class BTWait : BTNode
{
public float Duration { get; set; }
private float startTime;
private bool started = false;
public BTWait(float duration, string name = "Wait") : base(name)
{
Duration = duration;
}
public override BTStatus Tick()
{
if (!started)
{
startTime = Time.time;
started = true;
}
if (Time.time - startTime >= Duration)
{
Reset();
return BTStatus.Success;
}
IsRunning = true;
return BTStatus.Running;
}
public override void Reset()
{
base.Reset();
started = false;
}
}
/// <summary>
/// Log node - Logs a message (for debugging)
/// </summary>
public class BTLog : BTNode
{
private string message;
private Func<BTContext, string> dynamicMessage;
public BTLog(string message, string name = "Log") : base(name)
{
this.message = message;
}
public BTLog(Func<BTContext, string> messageFunc, string name = "Log") : base(name)
{
this.dynamicMessage = messageFunc;
}
public override BTStatus Tick()
{
string msg = dynamicMessage != null ? dynamicMessage(Context) : message;
Debug.Log($"[BT] {msg}");
return BTStatus.Success;
}
}
/// <summary>
/// Set Value node - Sets a value in context
/// </summary>
public class BTSetValue<T> : BTNode
{
private string key;
private T value;
private Func<BTContext, T> valueFunc;
public BTSetValue(string key, T value, string name = "SetValue") : base(name)
{
this.key = key;
this.value = value;
}
public BTSetValue(string key, Func<BTContext, T> valueFunc, string name = "SetValue") : base(name)
{
this.key = key;
this.valueFunc = valueFunc;
}
public override BTStatus Tick()
{
T val = valueFunc != null ? valueFunc(Context) : value;
Context.Set(key, val);
return BTStatus.Success;
}
}
/// <summary>
/// Check Value node - Checks a value in context
/// </summary>
public class BTCheckValue<T> : BTNode where T : IEquatable<T>
{
private string key;
private T expectedValue;
private Func<T, T, bool> comparer;
public BTCheckValue(string key, T expectedValue, string name = "CheckValue") : base(name)
{
this.key = key;
this.expectedValue = expectedValue;
}
public BTCheckValue(string key, T expectedValue, Func<T, T, bool> comparer, string name = "CheckValue") : base(name)
{
this.key = key;
this.expectedValue = expectedValue;
this.comparer = comparer;
}
public override BTStatus Tick()
{
var value = Context.Get<T>(key);
bool match;
if (comparer != null)
{
match = comparer(value, expectedValue);
}
else
{
match = value != null && value.Equals(expectedValue);
}
return match ? BTStatus.Success : BTStatus.Failure;
}
}
/// <summary>
/// Move To node - Moves towards a target position
/// </summary>
public class BTMoveTo : BTNode
{
public float Speed { get; set; } = 5f;
public float StoppingDistance { get; set; } = 0.5f;
private Vector3 targetPosition;
private Func<BTContext, Vector3> targetFunc;
public BTMoveTo(Vector3 target, string name = "MoveTo") : base(name)
{
targetPosition = target;
}
public BTMoveTo(Func<BTContext, Vector3> targetFunc, string name = "MoveTo") : base(name)
{
this.targetFunc = targetFunc;
}
public override BTStatus Tick()
{
if (Context?.Transform == null) return BTStatus.Failure;
Vector3 target = targetFunc != null ? targetFunc(Context) : targetPosition;
Vector3 currentPos = Context.Transform.position;
float distance = Vector3.Distance(currentPos, target);
if (distance <= StoppingDistance)
{
IsRunning = false;
return BTStatus.Success;
}
// Move towards target
Vector3 direction = (target - currentPos).normalized;
Context.Transform.position += direction * Speed * Time.deltaTime;
// Face direction
if (direction != Vector3.zero)
{
direction.y = 0;
if (direction.sqrMagnitude > 0.001f)
{
Context.Transform.forward = direction;
}
}
IsRunning = true;
return BTStatus.Running;
}
}
/// <summary>
/// Look At node - Rotates to face a target
/// </summary>
public class BTLookAt : BTNode
{
public float RotationSpeed { get; set; } = 5f;
public float Tolerance { get; set; } = 5f; // degrees
private Transform target;
private Func<BTContext, Transform> targetFunc;
public BTLookAt(Transform target, string name = "LookAt") : base(name)
{
this.target = target;
}
public BTLookAt(Func<BTContext, Transform> targetFunc, string name = "LookAt") : base(name)
{
this.targetFunc = targetFunc;
}
public override BTStatus Tick()
{
if (Context?.Transform == null) return BTStatus.Failure;
Transform lookTarget = targetFunc != null ? targetFunc(Context) : target;
if (lookTarget == null) return BTStatus.Failure;
Vector3 direction = lookTarget.position - Context.Transform.position;
direction.y = 0;
if (direction.sqrMagnitude < 0.001f)
{
return BTStatus.Success;
}
Quaternion targetRotation = Quaternion.LookRotation(direction);
float angle = Quaternion.Angle(Context.Transform.rotation, targetRotation);
if (angle <= Tolerance)
{
return BTStatus.Success;
}
Context.Transform.rotation = Quaternion.Slerp(
Context.Transform.rotation,
targetRotation,
RotationSpeed * Time.deltaTime
);
IsRunning = true;
return BTStatus.Running;
}
}
/// <summary>
/// Random Success node - Randomly returns success or failure
/// </summary>
public class BTRandomSuccess : BTNode
{
public float SuccessChance { get; set; } = 0.5f;
public BTRandomSuccess(float chance = 0.5f, string name = "RandomSuccess") : base(name)
{
SuccessChance = Mathf.Clamp01(chance);
}
public override BTStatus Tick()
{
return UnityEngine.Random.value < SuccessChance ? BTStatus.Success : BTStatus.Failure;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3f5de2b2723dd433bac886ded1d38724
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/BehaviorTree/BTLeaves.cs
uploadId: 920982
@@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace SynapticPro.BehaviorTree
{
/// <summary>
/// Node execution status
/// </summary>
public enum BTStatus
{
Success,
Failure,
Running
}
/// <summary>
/// Base class for all Behavior Tree nodes
/// </summary>
public abstract class BTNode
{
public string Name { get; set; }
public BTNode Parent { get; protected set; }
public bool IsRunning { get; protected set; }
/// <summary>
/// Context data shared across the tree
/// </summary>
public BTContext Context { get; set; }
protected BTNode(string name = "Node")
{
Name = name;
}
/// <summary>
/// Execute this node
/// </summary>
public abstract BTStatus Tick();
/// <summary>
/// Reset node state
/// </summary>
public virtual void Reset()
{
IsRunning = false;
}
/// <summary>
/// Called when node is aborted
/// </summary>
public virtual void Abort()
{
IsRunning = false;
}
/// <summary>
/// Set parent node
/// </summary>
internal void SetParent(BTNode parent)
{
Parent = parent;
}
}
/// <summary>
/// Base class for nodes with children
/// </summary>
public abstract class BTComposite : BTNode
{
protected List<BTNode> children = new List<BTNode>();
protected int currentChildIndex = 0;
public IReadOnlyList<BTNode> Children => children;
protected BTComposite(string name = "Composite") : base(name) { }
/// <summary>
/// Add a child node
/// </summary>
public BTComposite AddChild(BTNode child)
{
if (child != null)
{
child.SetParent(this);
child.Context = Context;
children.Add(child);
}
return this;
}
/// <summary>
/// Add multiple children
/// </summary>
public BTComposite AddChildren(params BTNode[] nodes)
{
foreach (var node in nodes)
{
AddChild(node);
}
return this;
}
/// <summary>
/// Remove a child node
/// </summary>
public void RemoveChild(BTNode child)
{
if (child != null)
{
child.SetParent(null);
children.Remove(child);
}
}
/// <summary>
/// Clear all children
/// </summary>
public void ClearChildren()
{
foreach (var child in children)
{
child.SetParent(null);
}
children.Clear();
currentChildIndex = 0;
}
public override void Reset()
{
base.Reset();
currentChildIndex = 0;
foreach (var child in children)
{
child.Reset();
}
}
public override void Abort()
{
base.Abort();
foreach (var child in children)
{
child.Abort();
}
}
}
/// <summary>
/// Base class for decorator nodes (single child)
/// </summary>
public abstract class BTDecorator : BTNode
{
protected BTNode child;
public BTNode Child => child;
protected BTDecorator(string name = "Decorator") : base(name) { }
/// <summary>
/// Set the child node
/// </summary>
public BTDecorator SetChild(BTNode node)
{
if (child != null)
{
child.SetParent(null);
}
child = node;
if (child != null)
{
child.SetParent(this);
child.Context = Context;
}
return this;
}
public override void Reset()
{
base.Reset();
child?.Reset();
}
public override void Abort()
{
base.Abort();
child?.Abort();
}
}
/// <summary>
/// Shared context data for the behavior tree
/// </summary>
public class BTContext
{
private Dictionary<string, object> data = new Dictionary<string, object>();
/// <summary>
/// The GameObject this tree is attached to
/// </summary>
public GameObject GameObject { get; set; }
/// <summary>
/// The Transform of the GameObject
/// </summary>
public Transform Transform => GameObject?.transform;
/// <summary>
/// Set a value in the context
/// </summary>
public void Set<T>(string key, T value)
{
data[key] = value;
}
/// <summary>
/// Get a value from the context
/// </summary>
public T Get<T>(string key, T defaultValue = default)
{
if (data.TryGetValue(key, out var value) && value is T typedValue)
{
return typedValue;
}
return defaultValue;
}
/// <summary>
/// Check if a key exists
/// </summary>
public bool Has(string key)
{
return data.ContainsKey(key);
}
/// <summary>
/// Remove a value
/// </summary>
public void Remove(string key)
{
data.Remove(key);
}
/// <summary>
/// Clear all data
/// </summary>
public void Clear()
{
data.Clear();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: cf24ecfaec86e41f3a4b25d6f3979fa4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/BehaviorTree/BTNode.cs
uploadId: 920982
@@ -0,0 +1,443 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace SynapticPro.BehaviorTree
{
/// <summary>
/// MonoBehaviour that runs a Behavior Tree
/// </summary>
public class BehaviorTreeRunner : MonoBehaviour
{
[Header("Execution Settings")]
[SerializeField] private bool autoStart = true;
[SerializeField] private float tickInterval = 0f; // 0 = every frame
[SerializeField] private bool pauseWhenDisabled = true;
[Header("Debug")]
[SerializeField] private bool debugMode = false;
[SerializeField] private string currentNodeName = "";
[Header("Events")]
public UnityEvent OnTreeStarted;
public UnityEvent OnTreeCompleted;
public UnityEvent OnTreeSucceeded;
public UnityEvent OnTreeFailed;
// Runtime
private BTNode rootNode;
private BTContext context;
private float lastTickTime;
private bool isRunning;
private BTStatus lastStatus = BTStatus.Running;
/// <summary>
/// The root node of the tree
/// </summary>
public BTNode RootNode => rootNode;
/// <summary>
/// The shared context
/// </summary>
public BTContext Context => context;
/// <summary>
/// Whether the tree is currently running
/// </summary>
public bool IsRunning => isRunning;
/// <summary>
/// Last tick status
/// </summary>
public BTStatus LastStatus => lastStatus;
private void Awake()
{
context = new BTContext { GameObject = gameObject };
}
private void Start()
{
if (autoStart && rootNode != null)
{
StartTree();
}
}
private void Update()
{
if (!isRunning || rootNode == null) return;
// Check tick interval
if (tickInterval > 0 && Time.time - lastTickTime < tickInterval)
{
return;
}
lastTickTime = Time.time;
// Tick the tree
lastStatus = rootNode.Tick();
if (debugMode && rootNode != null)
{
currentNodeName = GetCurrentNodeName(rootNode);
}
// Handle completion
if (lastStatus != BTStatus.Running)
{
OnTreeCompleted?.Invoke();
if (lastStatus == BTStatus.Success)
{
OnTreeSucceeded?.Invoke();
if (debugMode) Debug.Log($"[BT] Tree completed with Success");
}
else
{
OnTreeFailed?.Invoke();
if (debugMode) Debug.Log($"[BT] Tree completed with Failure");
}
// Reset for next run
rootNode.Reset();
}
}
private void OnEnable()
{
if (pauseWhenDisabled && rootNode != null)
{
// Resume
}
}
private void OnDisable()
{
if (pauseWhenDisabled && rootNode != null)
{
// Pause (just stop ticking, state preserved)
}
}
/// <summary>
/// Set the root node of the tree
/// </summary>
public void SetTree(BTNode root)
{
rootNode = root;
if (rootNode != null)
{
PropagateContext(rootNode, context);
}
}
/// <summary>
/// Start running the tree
/// </summary>
public void StartTree()
{
if (rootNode == null)
{
Debug.LogWarning("[BT] Cannot start tree: root node is null");
return;
}
isRunning = true;
lastTickTime = Time.time;
lastStatus = BTStatus.Running;
OnTreeStarted?.Invoke();
if (debugMode) Debug.Log("[BT] Tree started");
}
/// <summary>
/// Stop the tree
/// </summary>
public void StopTree()
{
if (!isRunning) return;
isRunning = false;
rootNode?.Abort();
rootNode?.Reset();
if (debugMode) Debug.Log("[BT] Tree stopped");
}
/// <summary>
/// Pause the tree
/// </summary>
public void PauseTree()
{
isRunning = false;
if (debugMode) Debug.Log("[BT] Tree paused");
}
/// <summary>
/// Resume the tree
/// </summary>
public void ResumeTree()
{
isRunning = true;
if (debugMode) Debug.Log("[BT] Tree resumed");
}
/// <summary>
/// Reset and restart the tree
/// </summary>
public void RestartTree()
{
StopTree();
StartTree();
}
/// <summary>
/// Set a value in the context
/// </summary>
public void SetContextValue<T>(string key, T value)
{
context?.Set(key, value);
}
/// <summary>
/// Get a value from the context
/// </summary>
public T GetContextValue<T>(string key, T defaultValue = default)
{
return context != null ? context.Get(key, defaultValue) : defaultValue;
}
/// <summary>
/// Propagate context to all nodes
/// </summary>
private void PropagateContext(BTNode node, BTContext ctx)
{
if (node == null) return;
node.Context = ctx;
if (node is BTComposite composite)
{
foreach (var child in composite.Children)
{
PropagateContext(child, ctx);
}
}
else if (node is BTDecorator decorator)
{
PropagateContext(decorator.Child, ctx);
}
}
/// <summary>
/// Get the name of currently executing node
/// </summary>
private string GetCurrentNodeName(BTNode node)
{
if (node == null) return "";
if (node.IsRunning)
{
if (node is BTComposite composite)
{
foreach (var child in composite.Children)
{
string childName = GetCurrentNodeName(child);
if (!string.IsNullOrEmpty(childName))
{
return childName;
}
}
}
else if (node is BTDecorator decorator)
{
string childName = GetCurrentNodeName(decorator.Child);
if (!string.IsNullOrEmpty(childName))
{
return childName;
}
}
return node.Name;
}
return "";
}
private void OnDrawGizmosSelected()
{
if (!debugMode || rootNode == null) return;
// Draw current node info
Vector3 pos = transform.position + Vector3.up * 2f;
#if UNITY_EDITOR
UnityEditor.Handles.Label(pos, $"BT: {currentNodeName}\nStatus: {lastStatus}");
#endif
}
}
/// <summary>
/// Builder for creating Behavior Trees fluently
/// </summary>
public class BehaviorTreeBuilder
{
private BTNode root;
private BTNode current;
/// <summary>
/// Start with a Selector
/// </summary>
public BehaviorTreeBuilder Selector(string name = "Selector")
{
var node = new BTSelector(name);
SetNode(node);
return this;
}
/// <summary>
/// Start with a Sequence
/// </summary>
public BehaviorTreeBuilder Sequence(string name = "Sequence")
{
var node = new BTSequence(name);
SetNode(node);
return this;
}
/// <summary>
/// Start with a Parallel
/// </summary>
public BehaviorTreeBuilder Parallel(string name = "Parallel")
{
var node = new BTParallel(name);
SetNode(node);
return this;
}
/// <summary>
/// Add an Action
/// </summary>
public BehaviorTreeBuilder Action(Func<BTContext, BTStatus> action, string name = "Action")
{
var node = new BTAction(action, name);
AddToCurrentComposite(node);
return this;
}
/// <summary>
/// Add a simple Action (auto-succeeds)
/// </summary>
public BehaviorTreeBuilder Do(Action<BTContext> action, string name = "Action")
{
var node = new BTAction(action, name);
AddToCurrentComposite(node);
return this;
}
/// <summary>
/// Add a Condition
/// </summary>
public BehaviorTreeBuilder Condition(Func<BTContext, bool> condition, string name = "Condition")
{
var node = new BTCondition(condition, name);
AddToCurrentComposite(node);
return this;
}
/// <summary>
/// Add a Wait
/// </summary>
public BehaviorTreeBuilder Wait(float duration, string name = "Wait")
{
var node = new BTWait(duration, name);
AddToCurrentComposite(node);
return this;
}
/// <summary>
/// Add an Inverter decorator
/// </summary>
public BehaviorTreeBuilder Invert()
{
// This should wrap the next node added
// For simplicity, we'll handle this differently
return this;
}
/// <summary>
/// Build the tree
/// </summary>
public BTNode Build()
{
return root;
}
/// <summary>
/// Build and assign to runner
/// </summary>
public void BuildAndRun(BehaviorTreeRunner runner)
{
runner.SetTree(Build());
runner.StartTree();
}
private void SetNode(BTNode node)
{
if (root == null)
{
root = node;
}
else if (current is BTComposite composite)
{
composite.AddChild(node);
}
current = node;
}
private void AddToCurrentComposite(BTNode node)
{
if (current is BTComposite composite)
{
composite.AddChild(node);
}
else if (root == null)
{
root = node;
current = node;
}
}
}
/// <summary>
/// Static helper for building trees
/// </summary>
public static class BT
{
public static BTSelector Selector(string name = "Selector") => new BTSelector(name);
public static BTSequence Sequence(string name = "Sequence") => new BTSequence(name);
public static BTParallel Parallel(string name = "Parallel") => new BTParallel(name);
public static BTRandomSelector RandomSelector(string name = "RandomSelector") => new BTRandomSelector(name);
public static BTRandomSequence RandomSequence(string name = "RandomSequence") => new BTRandomSequence(name);
public static BTInverter Inverter(string name = "Inverter") => new BTInverter(name);
public static BTSucceeder Succeeder(string name = "Succeeder") => new BTSucceeder(name);
public static BTFailer Failer(string name = "Failer") => new BTFailer(name);
public static BTRepeater Repeater(int count = -1, string name = "Repeater") => new BTRepeater(count, name);
public static BTCooldown Cooldown(float time, string name = "Cooldown") => new BTCooldown(time, name);
public static BTTimeout Timeout(float time, string name = "Timeout") => new BTTimeout(time, name);
public static BTRetry Retry(int count = 3, string name = "Retry") => new BTRetry(count, name);
public static BTDelay Delay(float time, string name = "Delay") => new BTDelay(time, name);
public static BTAction Action(Func<BTContext, BTStatus> action, string name = "Action") => new BTAction(action, name);
public static BTAction Action(Action<BTContext> action, string name = "Action") => new BTAction(action, name);
public static BTCondition Condition(Func<BTContext, bool> condition, string name = "Condition") => new BTCondition(condition, name);
public static BTWait Wait(float duration, string name = "Wait") => new BTWait(duration, name);
public static BTLog Log(string message, string name = "Log") => new BTLog(message, name);
public static BTMoveTo MoveTo(Vector3 target, string name = "MoveTo") => new BTMoveTo(target, name);
public static BTMoveTo MoveTo(Func<BTContext, Vector3> targetFunc, string name = "MoveTo") => new BTMoveTo(targetFunc, name);
public static BTRandomSuccess RandomSuccess(float chance = 0.5f, string name = "RandomSuccess") => new BTRandomSuccess(chance, name);
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fb8445612d6634f2dbe123206811c1b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Runtime/BehaviorTree/BehaviorTreeRunner.cs
uploadId: 920982