using System;
using UnityEngine;
namespace SynapticPro.BehaviorTree
{
///
/// Action node - Executes a function
///
public class BTAction : BTNode
{
private Func action;
private Action onStart;
private Action onEnd;
public BTAction(Func action, string name = "Action") : base(name)
{
this.action = action;
}
public BTAction(Action simpleAction, string name = "Action") : base(name)
{
this.action = (ctx) =>
{
simpleAction?.Invoke(ctx);
return BTStatus.Success;
};
}
///
/// Set callback for when action starts
///
public BTAction OnStart(Action callback)
{
onStart = callback;
return this;
}
///
/// Set callback for when action ends
///
public BTAction OnEnd(Action 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);
}
}
///
/// Condition node - Checks a condition
///
public class BTCondition : BTNode
{
private Func condition;
public BTCondition(Func 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;
}
}
///
/// Wait node - Waits for specified duration
///
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;
}
}
///
/// Log node - Logs a message (for debugging)
///
public class BTLog : BTNode
{
private string message;
private Func dynamicMessage;
public BTLog(string message, string name = "Log") : base(name)
{
this.message = message;
}
public BTLog(Func 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;
}
}
///
/// Set Value node - Sets a value in context
///
public class BTSetValue : BTNode
{
private string key;
private T value;
private Func valueFunc;
public BTSetValue(string key, T value, string name = "SetValue") : base(name)
{
this.key = key;
this.value = value;
}
public BTSetValue(string key, Func 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;
}
}
///
/// Check Value node - Checks a value in context
///
public class BTCheckValue : BTNode where T : IEquatable
{
private string key;
private T expectedValue;
private Func 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 comparer, string name = "CheckValue") : base(name)
{
this.key = key;
this.expectedValue = expectedValue;
this.comparer = comparer;
}
public override BTStatus Tick()
{
var value = Context.Get(key);
bool match;
if (comparer != null)
{
match = comparer(value, expectedValue);
}
else
{
match = value != null && value.Equals(expectedValue);
}
return match ? BTStatus.Success : BTStatus.Failure;
}
}
///
/// Move To node - Moves towards a target position
///
public class BTMoveTo : BTNode
{
public float Speed { get; set; } = 5f;
public float StoppingDistance { get; set; } = 0.5f;
private Vector3 targetPosition;
private Func targetFunc;
public BTMoveTo(Vector3 target, string name = "MoveTo") : base(name)
{
targetPosition = target;
}
public BTMoveTo(Func 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;
}
}
///
/// Look At node - Rotates to face a target
///
public class BTLookAt : BTNode
{
public float RotationSpeed { get; set; } = 5f;
public float Tolerance { get; set; } = 5f; // degrees
private Transform target;
private Func targetFunc;
public BTLookAt(Transform target, string name = "LookAt") : base(name)
{
this.target = target;
}
public BTLookAt(Func 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;
}
}
///
/// Random Success node - Randomly returns success or failure
///
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;
}
}
}