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