News › Forums › RAIN › Sample Projects, How To, and Code › Example XML Files OR A Tutorial For Wander, Eat, Attack, and Reset.
Tagged: AI, Behavior Tree, Behavior Tree example requests, motor, root motion, sample
This topic contains 55 replies, has 4 voices, and was last updated by Sigil 1 week ago.
-
AuthorPosts
-
September 7, 2022 at 10:14 am #39046
I think I have it working up to this point. Zombie moves to random locations, detects player and detects food using the Basic Motor. He gets stuck in the ConsumeFood custom action though, and does some weird tilting on random axis from time to time, like he’s staring at the ground on tippy-toes, or scanning the skies for the mothership. Detecting and chasing the player seems to straighten him out. The inevitable questions:
1) How do I animate with mechanim so that when he moves, he plays his BaseLayer.Walk animation? Every time I switch the zombie to mechanim motor/animations, the anim-box turns red, or indicates that it fired, but dude won’t even move anymore.
2)How do I either turn the Food entity off after a period of time, or make zombie dude less hungry so he returns to random walking around? Do I need to throw in an expression to get him out of the food loop for some period of time?
3) Custom actions seem to be where the hidden power is. Where can I learn more about them?
I appreciate your taking the time to waltz me through this exercise. This may turn into some good documentation, and who knows, I might just get the hang of this…
Mark
September 8, 2022 at 2:37 pm #39051I’ll be back later to work on this, but to address some of your issues:
Tilting:
Are you using a rigid body for your AI collision? If so make sure you constrain its rotations so that it only rotates around the Y axis.Walk Animation:
So if you setup the state machine I mentioned earlier, change the AI motor to a Mecanim Motor (third tab on the AI), and forward the Speed parameter (“Add Parameter” at the bottom of the motor), walking should work for you automatically. In fact I’m trying to avoid using the Animate node altogether (assuming that’s what you meant by the anim-box). The Animate node is kind of a holdover from the legacy animation system, and isn’t really necessary for mecanim.Custom Actions:
Unfortunately we don’t have much of a repository for previously made custom actions. You can search around the forums for examples (I’d use google or similar), or for smaller ones I can usually give examples pretty quickly.September 8, 2022 at 3:28 pm #39052Ahhh,damn those legacy left-overs. Figured anim was a required node. LOL Okay, I will keep on knocking it around and see if I can get zombie dude walking.
Cheers!
MarkSeptember 8, 2022 at 7:02 pm #39053Hmmm,
If I have a controller on the character, and Basic Motor, Basic Animator, zombie detects player, chases, and goes to last known location.If I have same rig, but Mecanim Motor, Speed Parameter, Basic Animator, zombie detects player, starts walking in forward direction, not towards player. Adding faceTarget=playerTarget has no effect. He just keeps walking. All 3 detect player elements fired, as I have each set to a different speed. That is working.
If I have same rig, but Mecanim Motor, Speed Parameter, Mecanim Animator (no anim states), He just keeps walking after detect.
In all 3 cases, the ChooseRandomLocation element fails and remains red.
No controller, dude is of course a tree. Detects player, but no movement, no nothing.
Am I doing something totally n00b here?
Mark- This reply was modified 4 months, 2 weeks ago by Sigil. Reason: fixed picture link
September 9, 2022 at 10:30 pm #39061For the AI just moving forward, make sure you turn on Override Root Rotation on the motor, otherwise the AI expects you to play turning animations in order to turn. I should have mentioned that (I forget that it isn’t on by default). Normally root motion includes rotation as well, but that makes for a more complicated state machine than I wanted to go into this time.
For the ChooseRandomLocation custom action, it requires a RAIN Navigation Mesh in order to work properly. The two failure cases are if it can’t find a graph to start with, or if it can’t find any polys to move towards. If you do have a Navigation Mesh, perhaps setup a couple Debug.Log messages where the FAILURE returns are and see what is happening (whether or not it can’t find the graph or if the poly search is failing).
Working on the next post right now.
September 9, 2022 at 11:23 pm #39064Now adding in an attack animation and game logic:
root sequencer parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget) 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: 20) move (move target: moveTarget) timer (seconds: random(1, 5)) move (move target: foodTarget, close enough distance: 1) mecanim parameter (parameter: Eat, parameter type: Trigger), value: true) timer (seconds: random(5, 10)) custom action (class: ConsumeFood, target: foodTarget) selector parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (aspect: "Player", form variable: playerTarget) sequencer move (move target: playerTarget) move (face target: playerTarget) mecanim parameter (parameter: Attack, parameter type: Trigger), value: true) custom action (class: AttackTarget, target: playerTarget) parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget) move (move target: playerTargetPosition)
So in the second to last parallel where it used to be:
parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (aspect: "Player", form variable: playerTarget) move (move target: playerTarget)
I’ve changed that now to be this:
parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (aspect: "Player", form variable: playerTarget) sequencer move (move target: playerTarget) move (face target: playerTarget) mecanim parameter (parameter: Attack, parameter type: Trigger), value: true) custom action (class: AttackTarget, target: playerTarget)
So what should happen now, is that while we are making sure we can still detect the player (and storing their previous position), we do the following (this is all in the sequencer):
- Get within our close enough distance to the player.
- Make sure we are facing the player.
- Set a trigger to play our attack animation.
- Run the AttackTarget custom action to do the actual game logic.
The separate move node with the face target is for making sure we’re still facing the player after the first attack. The AttackTarget custom action is also going to do a bit of extra work to make sure we’re actually playing the attack animation, and it won’t return success until it finishes.
Here is the AttackTarget custom action:
using RAIN.Action; using RAIN.Representation; using UnityEngine; [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; } // 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 return ActionResult.SUCCESS; } }
So the AttackTarget custom action will wait until the attack animation has played a certain amount (AttackTime). The AttackTime represents normalized time so it will always be from 0 to 1, 0 being the beginning of the state and 1 being the end. And after the damage has been done the custom action will continue to wait until the animation finishes playing.
So at this point I still haven’t put together a project to test all of these. The concepts are all sound though, I just may have some bugs. I’ll work on that next.
- This reply was modified 4 months, 2 weeks ago by Sigil. Reason: fixed whitespace
September 9, 2022 at 11:30 pm #39066Just as a note, you could change the state name we are waiting for in that custom action into an expression, and then pass it in from the outside. This would allow you to reuse this action for several different attacks.
Another way would be to put the trigger call inside the custom action itself, and make both the trigger name and the state name an expression. That might be a cleaner custom action, and it would still be usable for any attack.
Lots of ways to do this. I’ll leave it as it is for now.
September 12, 2022 at 10:35 am #39072Somehow my navmesh became null, had to regenerate. That took care of the non-moving zombie. Using the original example, zombie moves to a random location again, detects the player, and moves towards their location.
I required “Has Exit Time” enabled on the attack animation transition. Otherwise dude just twitched a lot. Now he launches into Attack state upon detecting the player. (Too far away.)
I added an “Attack Range” visual sensor, set close enough to 5, and then a detect with Sensor: AttackRange (Aspect: “Player”, Aspect Variable: inRange, Form Variable: playerTarget), under the seq-move-move before the mecparameter and AttackTarget custom action. Now he moves to the player and attacks at a reasonable distance.
I appreciate your continued efforts. Just trying to break the dependence.
BT now looks like:
root sequencer parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget) 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: 20) move (move target: moveTarget) timer (seconds: random(1, 5)) move (move target: foodTarget, close enough distance: 1) mecanim parameter (parameter: Eat, parameter type: Trigger), value: true) timer (seconds: random(5, 10)) custom action (class: ConsumeFood, target: foodTarget) selector parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (aspect: "Player", form variable: playerTarget) sequencer move (move target: playerTarget) move (face target: playerTarget) detect (repeat: UntilSuccess, Sensor:"AttackRange", Aspect:"Player", AspVar: inRange, FormVar:playerTarget) mecanim parameter (parameter: Attack, parameter type: Trigger), value: true) custom action (class: AttackTarget, target: playerTarget) parallel (fail: any, success: any, tie breaker: fail) detect (repeat: Until Success, aspect: "Player", form variable: playerTarget) move (move target: playerTargetPosition)
- This reply was modified 4 months, 1 week ago by Sigil. Reason: fixed behavior tree block
September 12, 2022 at 1:32 pm #39074Looks like I am still not getting the hang of this yet. Zombie dude DOES detect the player, move towards them, and when “inRange”, attacks. But after leaving the custom action “Attack”, he stays in attack animation.
I tried adding a condition after the move-move-detect, with a seq->mecparam(attack trigger true)->action->mecparam(attack trigger false)->expression(inRange=false). Debug on the last 2 indicate that they never fire. Also added ASPECT VARIABLE: inRange = false to all other player detects.
How wrong is this?
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: 20) move (move target: moveTarget) timer (seconds: random(1, 5)) move (move target: foodTarget, close enough distance: 1) mecanim parameter (parameter: Eat, parameter type: Trigger), value: true) timer (seconds: random(5, 10)) custom action (class: ConsumeFood, target: foodTarget) selector parallel (fail: any, success: any, tie breaker: fail) sequencer (repeat: Until Failure) expression (expression: playerTargetPosition = position(playerTarget), returns: Success) detect (aspect: "Player", form variable: playerTarget) sequencer move (move target: playerTarget) move (face target: playerTarget) detect (repeat: UntilSuccess, Sensor:"AttackRange", Aspect:"Player", AspVar: inRange, 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 PAR: (parameter: Attack, parameter type: Trigger), value: false) 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)
Added items are in caps.
September 12, 2022 at 2:15 pm #39075UH-OH! Zombie dude just found the food entity. He moved towards it, got close enough, and his animation had him stop, drop, and eat it. All good. After returning to the upright position, he continues to move forward. I assume the following happens after starting or finishing the ConsumeFood custom action. My console fills up with errors:
ArgumentException: GetComponent requires that the requested component 'Entity' derives from MonoBehaviour or Component or is an interface. UnityEngine.GameObject.GetComponentInChildren (System.Type type) (at C:/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineGameObjectBindings.gen.cs:59) UnityEngine.GameObject.GetComponentInChildren[Entity] () (at C:/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineGameObjectBindings.gen.cs:80) ConsumeFood.Execute (RAIN.Core.AI ai) (at Assets/AI/Actions/ConsumeFood.cs:23) RAIN.BehaviorTrees.BTActionNode.Execute (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTSequencerNode.Execute (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTParallelNode.Execute (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTSequencerNode.Execute (RAIN.Core.AI ai) RAIN.BehaviorTrees.BTNode.Run (RAIN.Core.AI ai) RAIN.Minds.BasicMind.Think () RAIN.Core.AI.Think () RAIN.Core.AIRig.AIUpdate () RAIN.Core.AIRig.Update ()
He finishes the eating animation, then resumes walking. Except that he just moves in a forward direction, ignoring all of the detects and what-not. He never turns, doesn’t ever pick another randomLocation, etc.
Any ideas?
MarkSeptember 13, 2022 at 8:39 am #39076I have modified the AttackTarget.cs by adding a bit of code.
_doneDamage = true; _animator.ResetTrigger ("Attack"); // Added to try and set the animation to NOT play. Debug.Log ("Successfully set ATTACK to false!");
This seems to have fixed the not coming out of attack animation.
Added private Animator _animator = null;
and ResetTrigger (Eat”) to ConsumeFood.cs, but it still barfs up previous error regarding “‘Entity’ derives from MonoBehaviour or Component or is an interface”.Also, new error in the console…
(Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.Memory.BasicMemoryEditor.DrawInspector (System.String aLabel, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.TypeEditors.RAINTypeEditor.DrawFieldForInspector (RAINEditor.Core.RAINComponentEditor aComponentEditor, System.String aLabel, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.TypeEditors.RAINTypeEditor.DrawFieldForInspector (RAINEditor.Core.RAINComponentEditor aEditor, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.TypeEditors.RAINTypeEditor.DrawFieldForInspector (RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.Core.AIEditor.DrawAIElements (System.String aLabel, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.Core.AIEditor.DrawInspector (System.String aLabel, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.TypeEditors.RAINTypeEditor.DrawFieldForInspector (RAINEditor.Core.RAINComponentEditor aComponentEditor, System.String aLabel, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.TypeEditors.RAINTypeEditor.DrawFieldForInspector (RAINEditor.Core.RAINComponentEditor aEditor, RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.Core.AIRigEditor.DrawComponentForInspector (RAIN.Serialization.FieldWalkerList aWalker) [0x00000] in <filename unknown>:0 at RAINEditor.Core.RAINComponentEditor.OnInspectorGUI () [0x00000] in <filename unknown>:0
Argh!
MarkSeptember 14, 2022 at 10:09 am #39086Alright, let’s slow down and take on one issue at a time. Having just one error is going to throw off the whole tree because it will either fail or throw an exception and that isn’t what we expect.
First the food, there is a mistake in the Custom Action that is causing the errors in the console. The line that gets the Entity should actually be getting the EntityRig. This would have kept the AI from deactivated the food after eating it, and the exception is just wreaking havoc on the tree.
... // 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; ...
I went back and updated the post, so you can copy the whole bit from there, if need be. Make sure the food eating is working as expected before moving on to the attack.
Next, the attacking. If you look at the original behavior tree that handles attacking, the only way the AI would ever fire the attack out of range is if the two move nodes above the trigger were to return success before you got close.
sequencer move (move target: playerTarget) move (face target: playerTarget) mecanim parameter (parameter: Attack, parameter type: Trigger), value: true) custom action (class: AttackTarget, target: playerTarget)
Well the first move node shouldn’t return success unless it is within its close enough distance of its target. If your close enough distance is really large (motor tab on the AI) that would cause this problem. You can also customize the close enough distance in the move node itself as well.
Also, I can’t recall if I mentioned it in this post yet, but you should be using the behavior tree editor to see what your AI are doing while your game is running. While the game is running and the AI in question is selected, open the behavior tree editor and set it to “Current AI” in the drop down. The behavior tree should be color coded based on the return values. It can still be hard to understand, but it will give you some clues to what is happening. Note that often times the behavior tree runs fast enough that you won’t see what happens unless you pause and step it.
Once we get that working we can address the Attack playing over and over, as it isn’t necessary to call ResetTrigger if the tree is working properly.
September 14, 2022 at 4:50 pm #39091Okay, 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….
MarkSeptember 17, 2022 at 6:38 am #39116Sigil,
This is all working, but the Attack part is a bit flaky. SOMETIMES, not always, when I leave the “AttackRange” sensor, the zombie continues to attack. I have had to make this a Bool, both in my AIcontroller, and in the associated scripts/BT. Leaving it as trigger seemed to fire too quickly. It would reset right away after firing, and the zombie would just twitch like it was starting the attack animation, then falling back to the locomotion anim, then firing the attack anim, then the loco again. Timers didn’t seem to help, as the trigger was still too quick to recover.
So the Bool’s are working, but they don’t seem to change reliably. Once every 4 or 5 times the zombie gets stuck in attack mode. Moving back into range resets it to not attack, and the next BT round sets it back to attack, fixing the issue. I probably need to check status of the Bool, and if its true already, set it to false.
I am satisfied that a little tweaking will fix this glitch, so what is the next step in our journey?
MarkSeptember 20, 2022 at 11:04 am #39167Still monkeying around with this. It appears that whenever a custom action terminates, it does not move down to the next object in the tree. So when I set the Attack=true as a Boolean, and then run the custom action, it never gets to the next line that sets Attack=false. Is this expected behavior? Am I missing a return ActionResult statement? Because it always shows running, even when I am no longer in detect or attack range.
Have you abandoned me? Sorry if I am making too many changes, or trying to move too fast, but I really want to learn how to get this working so I can move on to making some decent behaviors.
Mark
-
AuthorPosts
You must be logged in to reply to this topic.