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; } } }