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.

#39091

Mad_Mark
Participant

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