News › Forums › RAIN › Sample Projects, How To, and Code › Example XML Files OR A Tutorial For Wander, Eat, Attack, and Reset. › Reply To: Example XML Files OR A Tutorial For Wander, Eat, Attack, and Reset.
Okay, I repurposed the AttackTarget custom action using that EntityRig stuff. it appears to be working. w00t!
Just adding the EntityRig stuff to ConsumeFood toggled the isActive too quick. AI didn’t run the eating animation, just paused and then returned to patrolling. Running 2 Zombies on the AI-rig to ensure that they both feed, and then return to patrolling their random locations works.
How do I give the AI enough time to leave the area, say 30 - 60 seconds, then turn the foodTarget.isActive flag back on? Tried a timer, but of course that just pauses the active zombie… 8(
To get on the same page, here is where I am at.
BT:
root sequencer parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget, Aspectvar: inRange = false) sequencer (repeat: Forever) parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Food", form variable: foodTarget) sequencer (repeat: Forever) custom action (class: ChooseRandomLocation, target: moveTarget, distance: random(30, 60)) move (move target: moveTarget, moveSpeed: 1, FaceTarget: moveTarget, CloseEnuf: 3) timer (seconds: random(1, 5)) move (move target: foodTarget, closeEnuf: 2) mecanim parameter (parameter: Eat, parameter type: Trigger, value: true) timer (seconds: random(5, 10)) custom action (Repeat Until: Success, Class: ConsumeFood, target: foodTarget, EatTime: 3) selector parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (Sensor: "Sight", Aspect: "Player", AspectVar: inRange = false, FormVar: playerTarget) sequencer move (MoveTarget: playerTarget, MoveSpeed: 1.5, FaceTarget: playerTarget, CloseEnuf: 3) move (FaceTarget: playerTarget) detect (repeat: UntilSuccess, Sensor:"AttackRange", Aspect:"Player", FormVar: playerTarget, AspectVar: inRange = true) CONSTRAINT:(inRange = true) SEQ Mecanim parameter (parameter: Attack, parameter type: Trigger), value: true) Custom action (class: AttackTarget, target: playerTarget) Mecanim parameter (parameter: Attack, parameter type: Trigger), value: false) [Debug never fires] Expression (inRange = false) parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget, AspectVar: inRange = false) move (move target: playerTargetPosition, MoveSpeed: 1.5, FaceTarget: playerTargetPosition)
ChooseRandomLocation:
using UnityEngine; using System.Collections; using System.Collections.Generic; using RAIN.Action; using RAIN.Navigation; using RAIN.Navigation.Graph; using RAIN.Navigation.NavMesh; using RAIN.Representation; [RAINAction] public class ChooseRandomLocation : RAINAction { public Expression Target = new Expression (); public Expression Distance = new Expression (); public override ActionResult Execute (RAIN.Core.AI ai) { // Get any graphs at our AI's position List<RAINNavigationGraph> tGraphs = NavigationManager.Instance.GraphForPoint (ai.Kinematic.Position); if (tGraphs.Count == 0) return ActionResult.FAILURE; // Pretty much guaranteed it will be a navigation mesh NavMeshPathGraph tGraph = (NavMeshPathGraph)tGraphs [0]; // Look for polys within the given distance float tDistance = 20; if (Distance.IsValid) tDistance = Distance.Evaluate<float> (ai.DeltaTime, ai.WorkingMemory); // We'll use the recently added oct tree for this List<NavMeshPoly> tPolys = new List<NavMeshPoly> (); tGraph.PolyTree.GetCollisions (new Bounds (ai.Kinematic.Position, Vector3.one * tDistance * 2), tPolys); if (tPolys.Count == 0) return ActionResult.FAILURE; // Pick a random node NavMeshPoly tRandomPoly = tPolys [UnityEngine.Random.Range (0, tPolys.Count - 1)]; // If the user set a Target variable, use it if (Target.IsVariable) ai.WorkingMemory.SetItem<Vector3> (Target.VariableName, tRandomPoly.Position); // Otherwise just use some default else ai.WorkingMemory.SetItem<Vector3> ("randomLocation", tRandomPoly.Position); return ActionResult.SUCCESS; } }
AttackTarget:
using UnityEngine; using System.Collections; using System.Collections.Generic; using RAIN.Action; using RAIN.Entities; using RAIN.Representation; [RAINAction] public class AttackTarget : RAINAction { public Expression Target = new Expression (); public Expression AttackTime = new Expression (); private Animator _animator = null; private float _attackTime = 0.5f; private bool _doneDamage = false; public override void Start (RAIN.Core.AI ai) { base.Start (ai); _animator = ai.Body.GetComponent<Animator> (); if (AttackTime.IsValid) _attackTime = Mathf.Clamp01 (AttackTime.Evaluate<float> (ai.DeltaTime, ai.WorkingMemory)); _doneDamage = false; } public override ActionResult Execute (RAIN.Core.AI ai) { if (!Target.IsValid) return ActionResult.FAILURE; // We'll just grab this here for reference later AnimatorStateInfo tCurrentState = _animator.GetCurrentAnimatorStateInfo (0); // If we haven't done damage yet, we're just starting out so if (!_doneDamage) { // There are many ways to do the timing for the attack, in this case // I'll go the easy route and just wait until the attack time has been hit if (tCurrentState.IsName ("Base Layer.Attack") && tCurrentState.normalizedTime >= _attackTime) { // Here you should call out to your player and make them take damage in some way GameObject tTarget = Target.Evaluate<GameObject> (ai.DeltaTime, ai.WorkingMemory); _doneDamage = true; _animator.ResetTrigger ("Attack"); // Added to try and set the animation to NOT play. Debug.Log ("Successfully set ATTACK to false!"); } // We'll always return running until we do damage return ActionResult.RUNNING; } // In this case, we just need to wait for our animation to finish // you may need to check for transitioning here too if (tCurrentState.IsName ("Base Layer.Attack")) return ActionResult.RUNNING; // And we finished attacking //_animator.ResetTrigger ("Attack"); return ActionResult.SUCCESS; } }
ConsumeFood:
using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using RAIN.Action; using RAIN.Core; using RAIN.Entities; using RAIN.Representation; using RAIN.Navigation; using RAIN.Motion; [RAINAction] public class ConsumeFood : RAINAction { public Expression Target = new Expression (); public Expression EatTime = new Expression (); private Animator _animator = null; private float _eatTime = 0.5f; private bool _doneEating = false; public override void Start (RAIN.Core.AI ai) { base.Start (ai); _animator = ai.Body.GetComponent<Animator> (); if (EatTime.IsValid) _eatTime = Mathf.Clamp01 (EatTime.Evaluate<float> (ai.DeltaTime, ai.WorkingMemory)); _doneEating = false; } public override ActionResult Execute (RAIN.Core.AI ai) { if (!Target.IsValid) return ActionResult.FAILURE; // We'll just grab this here for reference later AnimatorStateInfo tCurrentState = _animator.GetCurrentAnimatorStateInfo (0); // If we haven't done damage yet, we're just starting out so if (!_doneEating) { // There are many ways to do the timing for the attack, in this case // I'll go the easy route and just wait until the attack time has been hit if (tCurrentState.IsName ("Base Layer.ZfeedingFull") && tCurrentState.normalizedTime >= _eatTime) { // Here you should call out to your player and make them take damage in some way GameObject tTarget = Target.Evaluate<GameObject> (ai.DeltaTime, ai.WorkingMemory); _doneEating = true; _animator.ResetTrigger ("Eat"); // Added to try and set the animation to NOT play. Debug.Log ("Successfully set EAT to false!"); // Grab the entity rig off of our target (again assuming it was detected properly) and mark it inactive EntityRig tEntityRig = tTarget.GetComponentInChildren<EntityRig> (); tEntityRig.Entity.IsActive = false; Debug.Log ("Successfully set FoodEntity.isActive to false!"); // Need a way to wait 60 seconds, then reactivate. } // We'll always return running until we do damage return ActionResult.RUNNING; } // In this case, we just need to wait for our animation to finish // you may need to check for transitioning here too if (tCurrentState.IsName ("Base Layer.ZfeedingFull")) return ActionResult.RUNNING; // And we finished Eating //_animator.ResetTrigger ("Eat"); //Debug.Log ("Second set FoodEntity.isActive to false!"); // Need a way to wait 60 seconds, then reactivate. return ActionResult.SUCCESS; } }
inRange is used to initiate attacks using a second smaller visual sensor. Working so far….
Mark