using System; using UnityEngine; using System.Linq; using Pathfinding.RVO; namespace Pathfinding.Examples.RTS { public enum Status { Invalid, Failure, Success, Running }; public class BTContext { public RTSUnit unit; public Transform transform; public Animator animator; } /// Implements a simple behavior tree. This is the base class for all nodes in the tree. public abstract class BTNode { protected Status lastStatus; public Status Tick (BTContext ctx) { if (lastStatus == Status.Invalid) OnInit(ctx); lastStatus = DoTick(ctx); if (lastStatus == Status.Invalid) throw new System.Exception(); return lastStatus; } public void Terminate (BTContext ctx) { OnTerminate(ctx); lastStatus = Status.Invalid; } protected virtual void OnInit (BTContext ctx) { } protected virtual void OnTerminate (BTContext ctx) { } protected abstract Status DoTick(BTContext ctx); } public class BTTransparent : BTNode { public BTNode child; protected override void OnTerminate (BTContext ctx) { child.Terminate(ctx); } protected override Status DoTick (BTContext ctx) { return child.Tick(ctx); } } public class Once : BTNode { public BTNode child; public Once (BTNode child) { this.child = child; } protected override void OnTerminate (BTContext ctx) { if (lastStatus == Status.Running) child.Terminate(ctx); } protected override Status DoTick (BTContext ctx) { if (lastStatus == Status.Success) return Status.Success; var s = child.Tick(ctx); if (s == Status.Success) { child.Terminate(ctx); } return s; } } public class SimpleAction : BTNode { public System.Action action; public SimpleAction (System.Action action) { this.action = action; } protected override Status DoTick (BTContext ctx) { action(ctx); return Status.Success; } } public class Condition : BTNode { public System.Func predicate; public Condition (System.Func predicate) { this.predicate = predicate; } protected override Status DoTick (BTContext ctx) { return predicate(ctx) ? Status.Success : Status.Failure; } } public class BTSequence : BTNode { public BTNode[] children; int childIndex = -1; public BTSequence (BTNode[] children) { this.children = children; } protected override void OnInit (BTContext ctx) { childIndex = 0; } protected override void OnTerminate (BTContext ctx) { for (int i = 0; i <= childIndex; i++) { children[i].Terminate(ctx); } childIndex = -1; } protected override Status DoTick (BTContext ctx) { int i; for (i = 0; i < children.Length; i++) { var s = children[i].Tick(ctx); if (s != Status.Success) { // Terminate all nodes that executed the last frame, but did not execute this frame for (int j = i + 1; j <= childIndex; j++) children[j].Terminate(ctx); childIndex = i; return s; } } childIndex = i - 1; return Status.Success; } } public class BTSelector : BTNode { public BTNode[] children; int childIndex = -1; public BTSelector (BTNode[] children) { this.children = children; } protected override void OnInit (BTContext ctx) { } protected override void OnTerminate (BTContext ctx) { for (int i = 0; i <= childIndex; i++) { children[i].Terminate(ctx); } childIndex = -1; } protected override Status DoTick (BTContext ctx) { int i; for (i = 0; i < children.Length; i++) { var s = children[i].Tick(ctx); if (s != Status.Failure) { // Terminate all nodes that executed the last frame, but did not execute this frame for (int j = i + 1; j <= childIndex; j++) children[j].Terminate(ctx); childIndex = i; return s; } } childIndex = i - 1; return Status.Failure; } } class Binding { T val; public System.Func getter; public System.Action setter; public T value { get { if (getter != null) return getter(); return val; } set { if (setter != null) setter(value); val = value; } } } public struct Value { T val; Binding binding; public T value { get { if (binding != null) return binding.value; return val; } set { if (binding != null) binding.value = value; val = value; } } public void Bind (ref Value other) { if (other.binding != null && binding == null) binding = other.binding; else if (binding == null && other.binding != null) other.binding = binding; else if (binding == null) binding = other.binding = new Binding(); else throw new System.Exception("Too complex binding"); } public void Bind (System.Func other) { if (binding != null) throw new System.Exception("Already has a binding"); binding = new Binding(); binding.getter = other; binding.setter = _ => { throw new System.InvalidOperationException("Trying to assign a value which has been bound as read-only (using a delegate)"); }; } public Value Bound { get { var r = this; if (binding == null) Bind(ref r); return r; } } public Value (System.Func getter) { val = default(T); binding = null; Bind(getter); } public static implicit operator Value(System.Func getter) { var val = new Value(); val.Bind(getter); return val; } } public class BTMove : BTNode { public Value destination; public BTMove (Value destination) { this.destination = destination; } protected override void OnInit (BTContext ctx) { ctx.unit.SetDestination(destination.value, MovementMode.Move); } protected override Status DoTick (BTContext ctx) { var dest = destination.value; if ((Time.frameCount % 100) == 0) ctx.unit.SetDestination(dest, MovementMode.Move); if (VectorMath.SqrDistanceXZ(ctx.transform.position, dest) < 0.5f * 0.5f) { return Status.Success; } else { return Status.Running; } } } public class FindClosestUnit : BTNode { public Value target; public bool reserve; RTSUnit.Type type; public FindClosestUnit (RTSUnit.Type type) { this.type = type; } protected override void OnTerminate (BTContext ctx) { if (reserve && target.value != null) { if (target.value.reservedBy != ctx.unit) throw new System.Exception(); target.value.reservedBy = null; } target.value = null; } RTSUnit FindClosest (Vector3 point) { var units = RTSManager.instance.units.units; RTSUnit closest = null; var dist = float.PositiveInfinity; for (int i = 0; i < units.Count; i++) { var unit = units[i]; if (unit.type != type || (reserve && unit.reservedBy != null)) { continue; } if (unit.resource != null && !unit.resource.harvestable) { continue; } var d = (unit.transform.position - point).sqrMagnitude; if (d < dist) { dist = d; closest = unit; } } return closest; } protected override Status DoTick (BTContext ctx) { if (target.value != null) { return Status.Success; } target.value = FindClosest(ctx.transform.position); if (target.value != null) { if (reserve) target.value.reservedBy = ctx.unit; return Status.Success; } return Status.Failure; } } static class Behaviors { public static BTNode HarvestBehavior () { var reserve = new FindClosestUnit(RTSUnit.Type.ResourceCrystal) { reserve = true }; var dropoff = new FindClosestUnit(RTSUnit.Type.HarvesterDropoff) { reserve = true }; var dropoffQueue = new FindClosestUnit(RTSUnit.Type.HarvesterDropoffQueue); return new BTSelector(new BTNode[] { new HarvestMode() { child = new BTSelector(new BTNode[] { new BTSequence(new BTNode[] { new Condition(ctx => ctx.unit.storedCrystals > 0), new BTSequence(new BTNode[] { dropoff, new BTMove(new Value(() => dropoff.target.value.transform.position)), new SimpleAction(ctx => { ctx.unit.owner.resources.AddResource(RTSUnit.Type.ResourceCrystal, ctx.unit.storedCrystals); ctx.unit.storedCrystals = 0; }), }) //new Deposit(move1), }), new BTSequence(new BTNode[] { new Condition(ctx => ctx.unit.storedCrystals == 0), new BTSequence(new BTNode[] { reserve, new BTMove(new Value(() => reserve.target.value.transform.position)), new Harvest { resource = new Value(() => reserve.target.value.resource), duration = 5 }, }), }) }) }, new BTSequence(new BTNode[] { dropoffQueue, new BTMove(new Value(() => dropoffQueue.target.value.transform.position)), }) }); } } public class HarvestMode : BTTransparent { protected override void OnTerminate (BTContext ctx) { ctx.unit.GetComponent().layer = RVO.RVOLayer.Layer2; base.OnTerminate(ctx); } protected override Status DoTick (BTContext ctx) { var s = base.DoTick(ctx); ctx.unit.GetComponent().layer = s == Status.Running ? RVO.RVOLayer.Layer3 : RVO.RVOLayer.Layer2; return s; } } public class Harvest : BTNode { public Value resource; public float duration = 5; float time; protected override void OnInit (BTContext ctx) { ctx.animator.SetBool("harvesting", true); //ctx.unit.locked = true; } protected override void OnTerminate (BTContext ctx) { Debug.Log("Terminated harvesting"); ctx.animator.SetBool("harvesting", false); } protected override Status DoTick (BTContext ctx) { time += Time.deltaTime; if (time > duration) { ctx.animator.SetBool("harvesting", false); if (ctx.animator.GetCurrentAnimatorStateInfo(0).IsName("RTSHarvesterHarvesting") || ctx.animator.GetCurrentAnimatorStateInfo(0).IsName("RTSHarvesterHarvestingExit")) { return Status.Running; } else { ctx.unit.storedCrystals += 50; resource.value.value -= 50; time = 0; //ctx.unit.locked = false; return Status.Success; } } else { return Status.Running; } } } public class BTHarvest { /*RTSCommandMove moveToDepositPoint; RTSCommandMove moveToHarvestPoint; void Deposit () { } public override void Tick () { if (HasResources()) { if (moveToDepositPoint.Tick() == Success) { Deposit(); } } else { var target = ReserveTarget(); moveToHarvestPoint.target = target; if (moveToHarvestPoint.Tick() == Success) { Harvest(target); } } }*/ } }