News › Forums › RAIN › General Discussion and Troubleshooting › RAIN Mecanim help
Tagged: animation, mecanim motor, mecparam
This topic contains 12 replies, has 3 voices, and was last updated by Sigil 5 months, 2 weeks ago.
-
AuthorPosts
-
January 25, 2023 at 3:11 pm #39957
Before i ask for help, I have been trying to figure this out for a week or so now. I have watched all of Unity’s and RAIN’s tutorial videos out there. A lot of them are old and thus don’t match up with the current RAIN asset.
Essentially all I am trying to do is make my AI patrol a certain area and pause after certain way point marks. I don’t want him to pause on EVERY single one but every now and then i want him to pause and idle for a bit.
i ran into this post http://rivaltheory.com/forums/topic/simple-waypoint-pause/. This didn’t really help me because I am completely lost on how RAIN interacts with the unity animator states.
I also ran into a few post’s where Prime said we should use the MecanimMotor and the MecanimAnimator to control the animation states but there isn’t any video i can reference to. Most of the tutorial videos out there already have the animations provided and use root motions.
The model i have has no root motion and only has a idle, walk , run , and attack animation.
What i really want is to learn how to use rain with animations properly. I can Provide a small portion of my current game just to show you what i have so far and how I’ve been going about it. If this is too much to ask then if you could just help me setting up to get my AI to take a random pause after he reaches a certain point and idle and then resume patrolling.
what i have so far is…
SEQ
-MecParam(Repeat=never,this sets my animation value to ‘walk’ essentialy)
-WAY
—MOVE(to next waypoint)
—MecParam(Repear=Never,This sets my animation value to ‘idle’)
—Timer(3 secs)When i run it he walks up to the first waypoint marker and then pauses and idles which is good! BUT when he starts moving again he’s in idle animation while moving which is wrong! If i set the first MecParam to Repeat = forever then when he pauses he just walks in place and the idle animation never happens.
As i said earlier i know i’m probably using Rain incorrectly with the animation but there isn’t a video i can reference too. I am willing to learn if someone can teach me.
PS: running off to work right now so i won’t be able to check this for about 5 hours
January 26, 2023 at 1:28 am #39970The reason he never goes back to walking is that the waypoint patrol node continues to run its children until the AI is done patrolling (if it is looping or ping pong, that’ll be forever). If it is the waypoint path node, it works similarly except it will stop once the AI gets to the end target.
So your first mecparam only gets hit the first time, and never again, as you noticed. You can move that first mecparam into the waypoint node, before the move, and it’ll start working the way you want.
If you want it to be random amount of time, and only sometimes occur, something like this would work (may have to change some of the details).
sequencer waypoint patrol (waypoint route: "Patrol Route", move target variable: _waypoint) mecparam (parameter: Speed, value: 1) move (move target: _waypoint) random sequencer // empty sequencer mecparam (parameter: Speed, value: 0) timer (seconds: random(1, 3))
So that would move to the first waypoint, and then randomly choose between the empty sequencer and the sequencer that has the idle and timer.
So once you get that working we can go down the route of using the MecanimMotor instead, as that’ll work better for you in the long run (for walking and running at least).
January 26, 2023 at 11:44 am #39974It worked! I feel silly considering I just had to move one node down into the waypoint node. He now walks around and goes into idle at random intervals.
Now I see that Random can use Weights to kind of control which node is more likely to get triggered over the other nodes. In my case i want him to patrol more often than pausing. I know it takes an expression but there doesn’t seem to any information on the wiki about how to use the weight. Is there anyway you could explain a little on how this works? If not its no big deal, i can just add a few more empty sequencer nodes to overcome that.
I also noticed that the mecparam you use in the example above use the parameter ‘Speed’ which is where the MecanimMotor comes in I am assuming? In my MecanimMotor and MecanimAnimator i have no parameters or states.
In my case, for my animator controller, the only parameters i have is AnimSpeed = float and Direction = float(at the moment I don’t understand how to get direction to work). When AnimSpeed is 0 he’s in idle state, if AnimSpeed>.10 then he goes into a walk and if AnimSpeed = 1 then he’s in running state.
so in my animator controller, i have an idle state and a locomotion state. He starts in idle and the only way for him to transition into locomotion is if AnimSpeed > .10. Now locomotion is a bleed tree with a walkleft,walkfoward,walkright, and run. Lets ignore direction for now and just worry about walking and running. In my game currently, you helped me fix the fact that he would get stuck in idle animation while moving, and it now does exactly what i want BUT there is a slight bug. He doesn’t get out of idle animation fast and enough. This means, and i’m staring at my animator controller do it, the idle state has to ALMOST finish running before he goes into walking animation again. So for about 1 or 2 seconds he’s moving while still in idle animation and THEN goes into walking animation. Obviously, an npc would look very odd doing this and thus I’d like help to know what you think i’m doing wrong or how i can overcome this.
I would really like to understand how the MecanimMotor and MecanimAnimator can help and i am assuming that the reason my model is running into these bugs is because I’m using rain incorrectly.
for MY own mecparam i have it as….
mecparam(parameter: AnimSpeed, value: 0) = idle
mecparam(parameter: AnimSpeed, value: .15) = walk
mecparam(parameter: AnimSpeed, value: 1) = runJanuary 26, 2023 at 12:15 pm #39975Starting with the random node and weights. Any child that goes into a random node will get a weight value that it can assign. You can use any range of numbers for the weight, but it is probably good to be consistent, so lets say we weight things from 0 to 1. If you have two children in a random and give them both 0.5, it’ll just be a 50/50 chance, as if you didn’t do anything. Give one .25 and one .75 it’ll be 25% and 75% respectively. So in your case I’d give the empty sequencer a value of 0.8 and the pausing one 0.2. That should make the empty sequencer happen more often (hopefully close to 80% more often) than the other one.
Next, to fix your animator controller. It sounds like you have Has Exit Time checked on your transitions to/from idle and your blend tree, you’ll want to turn that off. That would have caused the idle or walk/run animation to wait until it finished playing the loop it was on before transitioning.
Next up, using the MecanimMotor. So the MecanimMotor is similar to the BasicMotor but it has a few more options to help you out with locomotion. You aren’t using root motion so make sure that is unchecked to start off. At the very bottom of the MecanimMotor there is an Add Parameter option. This lets you forward values from RAIN to your animator controller. For now, add the Speed parameter and change the name of it to AnimSpeed (to match your state machine). That’s it, now the the MecanimMotor will handle your speed value for you automatically. You’ll want to remove the mecparam nodes from your behavior tree at this point as it will get handled for you.
So what you’ll probably notice right away, is that your AI will likely be running around, but not moving nearly fast enough for the animation. What you’ll want to do is increase your blend tree value for walking and running until the AI starts to look like his feet are actually connecting. At that point you can increase the default speed for the AI until he moves the way you like. You’ll just need to play around with the values for the most part unless you made the animation and know exactly how fast they animated the walk/run speeds.
0.15 is a little slow for walking, I’d start around 1 for walking, maybe 3 for running and start tweaking from there. I’d choose a default for the MecanimMotor speed to be whatever the AI does the most, probably patrol speed. In your behavior tree when you actually have him chase something you can increase his speed there.
As far as the MecanimAnimator is concerned, I would assign it in the editor but I wouldn’t use it. The states are a hold over from an older RAIN and will be going away very soon. They don’t do anything you can’t do in a mecparam though, and if you need more control than that a custom action would be the best bet.
January 26, 2023 at 2:39 pm #39977Thanks so much Sigil! Everything you’ve told me so far has worked flawlessly! i managed to get the mecanimMotor to do all the moving now and it works great!
I have just a few more questions regarding the behavior tree. As i have it now my NPC patrols and when he detects the player he runs after them which is perfect! BUT if the player takes a step to the left or right or just ‘S’ keys backwards the NPC loses sight of him, for just a second, and then chases him again. So what that ends up doing is making him stutter from a run to a walk back to a run very quickly but it’s heavily noticeable and he will keep doing it until you turn a corner or he finally catches you.
My tree looks like this. The first constraint is just his normal patrol route with a random idle added to it.
1) constraint(varPlayer==null) just patrols
2) constraint(varPlayer!=null) goes into constraint #3
3)constraint(varMelee==null) chases player until he’s in melee range
4)constraint(varMelee!=null) some attack animation haven’t got that far yetAnd then regarding his waypoint markers, when my NPC reaches any waypoint marker, because of how my map is laid out, most of them are sharp 90 degree angles. My NPC will just turn very quickly towards the next point. What I’m wondering is if there’s a way to tell him to take the turn slightly slower. Kind of like a transition to turning instead of just facing north and then suddenly looking east and having no delay to it.
January 26, 2023 at 3:15 pm #39978Well, as far as the stuttering, it depends on what it happening. If your player is still within the sensor’s range and it is stuttering, check to see if line of sight is causing it. What often happens is that the aspects default right at the feet of the AI, and occasionally dip below the planes you might be working on (or terrain or whatever) causing a line of sight block. So usually raising the aspect up to chest height fixes the issue. If the stutter is occurring because the player leaves the sensor’s range for a moment, then we’ll need to account for that in the behavior tree (I have some solutions for that).
For the waypoints: You can lower the AI’s rotation speed to make him turn slower, and you can lower the face before move angle to make him turn more before he starts moving forward. Rotation speed can be overwritten by a move node in the behavior tree, but the face before move angle is stuck on the motor (that may need to be changed).
January 26, 2023 at 3:26 pm #39979Thanks again Sigil! Let me play around with what you just said and I’ll let you know how it goes.
Running off to work but it looks like if i turn off all of the line of sight masks except for the players he doesn’t stutter but then he can see me through walls so i’ll have to play with it when i get back.
January 27, 2023 at 11:36 am #39983just an update
I fixed the stuttering! It was a line of sight mask issue. For anyone else who might run into this post, when you place the entity on your Player, assuming it’s a First Person controller, the entity will most likely be inside of your main camera. Make sure the layer of the main camera isn’t the same as the entity as it will cause the AI to stutter when he detects you. This is because the AI will constantly search for the entity and this entity will ‘clip’ into the main camera just for a second as you move your player.
I also managed to slow his turning down a bit for the way markers so it looks better now.
I haven’t tried to do this next part yet but just as a general direction, how would you go about having the AI detect you and then if you ran around a corner, i don’t want the AI to necessarily stop chasing you right away. I’d like the AI to keep chasing me but maybe after like 5-6 seconds of no line of sight then he could stop.
On that note, I have an issue where if i have my AI chases me for awhile, i do this on purpose of course, and then break line of sight after chasing me for awhile, he turns around and goes ALL the way back to a way point marker that was near the start. This isn’t much of a problem because i like the idea of him kind of back tracking so the player has no idea where he went but maybe in the future where i might not want that, I’d like to know in advance if there’s a way around this.
January 29, 2023 at 10:58 am #39995We have an expression function to help with that, called position.
Essentially you will need to record his position while you are chasing him, so that in the event you lose your target (but still have his position) you can head to it. I’d have to look at the overall behavior tree, but in general you can do something like this:
root parallel (succeed: any, fail: any, tie breaker: fail) detect (repeat: forever, form variable: myTarget) selector constraint (constraint: myTarget == null) waypoint patrol move selector constraint (constraint: myTarget != null) parallel (succeed: any, fail: any, tie breaker: fail) expression (repeat: forever, expression: myTargetPosition = position(myTarget)) move (move target: myTarget) constraint (constraint: myTarget == null) move (move target: myTargetPosition) expression (expression: debug("Lost my target"), returns: success)
I wrote it a bit differently then your original, but the general idea is that you need to handle the failure case of your chasing. Once you start chasing (and recording their position) if you fail it means you lost the target, so you can put a selector around that and handle the failure by continuing to head to the position. If you ever detect the target again, it’ll hit the constraint and fail and start at the top again.
As for your second issue, what would you like the AI to do instead of heading back to the original waypoints? If you wanted them to search around for the player instead, you could have a second waypoint network of search spots that he starts to follow, for instance. Whatever it is, this would end up being the success case for reaching the players last position, so where I have the debug “Lost my target”, you could put a sequencer, or another patrol setup, or have them do a dance, or anything you like.
- This reply was modified 5 months, 2 weeks ago by Sigil. Reason: fixed a bug in the behavior tree (per post below)
February 2, 2023 at 12:52 pm #40011Wow thanks Sigil! So recording the position of the player worked perfectly. My AI now chases the player a bit further even after losing line of sight which is exactly what i wanted.
For my second issue, just like you said, i added a second way point network and had him just start patrolling from there and it worked great!
I ran into a new issue after these couple of days. After fixing everything you said from above and got it to work i started implementing a way for my player to take damage after my AI does his attack animation.
So as you said in one of your comments, i added a custom action and got it to pass damage to my script attached to my player which just contains his health at the moment. Now the issue i ran into is that my AI runs through his tree so fast that it goes from 100 -> 0 in a fraction of a second. So obviously i added a timer to this. This way he does his attack animation then the custom action,which sends 20 points of damage to the player health script, waits 1 second and finally repeats the 3 steps i just listed. Okay so this works great! THEN i decided to add a random node where he randomly picks which attack animation he does, because obviously i want a little random in my game. Well, this is where the problem kicks in because what i have is…..
Random
-Sequencer
-Attack 1 animation
-Send 20 damage to health script
-Timer(1 second)
-Sequencer
-Attack 2 animation
-Send 15 damage to health script
-Timer(1 second)So Random will select one of the sequencer to run which is fine and dandy but for some reason after the timer goes off it gets out of that sequencer and go to the other.
So now let me list my problems…
1) Is using the timer node even the best option for the AI’s animation + sending damage? i feel that it’s very unreliable because the animation isn’t necessarily 1 second exactly which would cause the AI to being doing damage either slower or faster rate than his animation is.
2)Should i be trying to use check state node? If so how does it work? I’ve already tried to use it numerous times and it always seems to do something different than what i thought it was going to do.
3)How do i go about, just a general direction, having the AI do no damage after dealing damage? essentially giving the player invulnerability after taking damage to give him a chance to get away and somehow having the AI kind of just idle or taunt or something after doing damage.Edit: As i was posting this i realized that i didn’t have the sequencer node’s set to loop forever so that fixes part of my issue.
February 10, 2022 at 1:09 pm #40030Sorry for the late reply. Let’s start with the timer and check state nodes.
My recommendation for handling the AI’s animation and sending damage is a custom action. In particular, if you look at this post, it has a good example for handling animation + simple damage, and what I’ve personally used internally. You could take it a step further and have it trigger your attack as well, allowing you to skip the trigger in the behavior tree in addition to the timer. The check state was an attempt to handle this issue in the behavior tree but it just doesn’t work well for the advanced cases. Using a custom action allows you to handle the waiting for a particular animation and also time the attack for when the gun fires, or sword stabs, etc (so they don’t get attacked early/late).
For damage dealing in general, I’ve generally implemented this as a Custom AI Element or just a component on my AI. Something like this would work for the simple cases (you could easily make it better):
using System; using UnityEngine; [Serializable] public class DamageAndHealth : MonoBehaviour { [SerializeField] private float _health = 100; [SerializeField] private float _damage = 10; [SerializeField] private float _accuracy = 0.5f; [SerializeField] private float _incapacitationTime = 1; [SerializeField] private float _invulnerabilityTime = 1; private float _incapacitatedTimer = float.MinValue; private float _invulnerableTimer = float.MinValue; public float Health { get { return _health; } set { _health = value; } } public float Damage { get { return _damage; } set { _damage = value; } } public float Accuracy { get { return _accuracy; } set { _accuracy = value; } } public bool CanAttack { get { return (Time.time > _incapacitatedTimer); } } public bool CanTakeDamage { get { return (Time.time > _invulnerableTimer); } } public bool IsAlive { get { return (_health > 0); } } public bool Attack(DamageAndHealth aTarget) { // If we can't attack we're already done if (!CanAttack || !aTarget.CanTakeDamage) return false; // Figure out how much we will hurt them float tDamage = _damage - UnityEngine.Random.Range(0, Mathf.Clamp01(1 - _accuracy)) * _damage; // Do damage aTarget.TakeDamage(tDamage); // We'll consider an attack incapacitating (as we can't attack again for a moment) _incapacitatedTimer = Time.time + _incapacitationTime; return tDamage > 0; } public bool TakeDamage(float aDamage) { if (!CanTakeDamage) return false; // A very simple health _health = Mathf.Max(0, _health - aDamage); // Getting damage makes us invulnerable, but we can't attack either if (aDamage > 0) { _incapacitatedTimer = Time.time + _incapacitationTime; _invulnerableTimer = Time.time + _invulnerabilityTime; } return aDamage > 0; } }
And then I’d take the custom action that I linked, change it a bit, and call into my damage component:
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 DamageAndHealth _myHealth = null; private DamageAndHealth _targetHealth = 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>(); _myHealth = ai.Body.GetComponent<DamageAndHealth>(); if (Target.IsValid) { GameObject tTargetObject = Target.Evaluate<GameObject>(ai.DeltaTime, ai.WorkingMemory); if (tTargetObject != null) _targetHealth = tTargetObject.GetComponent<DamageAndHealth>(); } if (AttackTime.IsValid) _attackTime = Mathf.Clamp01(AttackTime.Evaluate<float>(ai.DeltaTime, ai.WorkingMemory)); _doneDamage = false; } public override ActionResult Execute(RAIN.Core.AI ai) { // If we can't attack or they can't take damage we failed if (_myHealth == null || !_myHealth.CanAttack || _targetHealth == null || !_targetHealth.CanTakeDamage) { 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) { _myHealth.Attack(_targetHealth); _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; } }
And I’d probably have another simple action to just tell if they are no longer alive:
using RAIN.Action; using RAIN.Representation; using UnityEngine; [RAINAction] public class IsTargetAlive : RAINAction { public Expression Target = new Expression(); private DamageAndHealth _targetHealth = null; public override void Start(RAIN.Core.AI ai) { base.Start(ai); if (Target.IsValid) { GameObject tTargetObject = Target.Evaluate<GameObject>(ai.DeltaTime, ai.WorkingMemory); if (tTargetObject != null) _targetHealth = tTargetObject.GetComponent<DamageAndHealth>(); } } public override ActionResult Execute(RAIN.Core.AI ai) { if (_targetHealth == null || !_targetHealth.IsAlive) return ActionResult.FAILURE; return ActionResult.SUCCESS; } }
So setting up a good behavior tree is a constant balancing act of deciding where to put your code. For me, what I tend to do is setup functions like I showed you in custom elements or components, and use my custom actions to call into that. It keeps my custom actions short, and it also allows me to access those same functions from components or other plugins that may not be able to interface with our AI closely.
Just a caveat, I put this together fairly quickly and more as an example then a definite way of putting it together. It doesn’t have to be this way, it’s just one way of looking at it.
February 24, 2022 at 10:48 am #40110Hey I think I found a small bug. With sequencer, it stops when the first constraint returns false. Anyway, this script is exactly what I was looking for. A simple hunt to go to the last known location before resuming the search.
root parallel (succeed: any, fail: any, tie breaker: fail) detect (repeat: forever, form variable: myTarget)
sequencer//SHOULD BE SELECTOR!!! constraint (constraint: myTarget == null) waypoint patrol move selector constraint (constraint: myTarget != null) parallel (succeed: any, fail: any, tie breaker: fail) expression (repeat: forever, expression: myTargetPosition = position(myTarget)) move (move target: myTarget) constraint (constraint: myTarget == null) move (move target: myTargetPosition) expression (expression: debug("Lost my target"), returns: success)- This reply was modified 5 months, 2 weeks ago by RAINLover.
February 25, 2022 at 6:00 pm #40123Thanks, I updated the post for anyone else who happens upon it.
-
AuthorPosts
You must be logged in to reply to this topic.