I’m trying to have a NavMeshAgent do several frames’ worth of movement in one. I’m hoping to read the velocity it plans to use and feed that into Move. I was trying the following:
if(Input.GetKeyDown(KeyCode.M))
{
NavMeshPath path = new NavMeshPath();
for(int i = 0; i < 100; i++)
{
navMeshAgent.Move(navMeshAgent.velocity);
Debug.Log(navMeshAgent.velocity); // This is the same value for all 100 steps
navMeshAgent.CalculatePath(targetWalkTo.transform.position, path);
navMeshAgent.SetPath(path);
}
}
The problem is that the velocity doesn’t update. The documentation for Move claims that the path is automatically adjusted but I put the path calculation/set in just in case.
Originally I had only one Move command per Update with GetKey instead of GetKeyDown. That didn’t work either.
What am I doing wrong here? Why does NavMeshAgent.velocity not update as the agent moves?
Ive never used NavmeshAgent.Move before, although from the API it reads as if the adjustments to the path are automatically done in the background. Maybe you can try setting your path once, and calling NavmeshAgent.Move after that.
In order to test this, I would forgo this for-loop approach which I doubt is actually accomplishing anything, and instead have a separate button to activate NavmeshAgent.Move with a fixed offset on command.
Your for loop is executing all in one frame. Unity locks up and does nothing until your code returns or yields.
One way to do what you want might be to turn off the .updatePosition boolean, but I have not tinkered with it, nor do I have confidence in that suggestion. You might want to look through the NavMeshAgent API to see what all it can do.
I’ve tried the above and every combination of settings that made sense, including turning .enabled off and on. No luck. I even tried some variations of using transform.position instead, based on this suggestion:
but velocity never gets updated. A few times the agent even went straight through a wall. I don’t know how I can get the velocity to update between Moves/transforms.
ol-reliable is getting the previous and current transform.position between frames, and dividing the delta by Time.deltaTime.
I went to look at what the API had to say about NavmeshAgent.velocity, and it reads:
“Reading the variable will return the current velocity of the agent based on the crowd simulation.”
, which makes me think the API is referring to the steering behavior when navmesh agents manage to get in each other’s way. Whatever it is however, it is a special condition and not the NavmeshAgent’s default behavior.
I guess that documentation is a bit deceptive as it works the same way with just one NavMeshAgent in the scene (as long as you don’t Move the agent). But I read that page (I thought I did before) and caught something else. It says:
“if releasing the control to the simulation, set the velocity to zero”
Tried that. That didn’t work either (NavMeshAgent just froze in place). Probably because
“If you set the value, the effect will show up in the next update”
Which probably explains why this stuff doesn’t work (as several steps in the same Update). Unity doesn’t process some parts of the NavMeshAgent movement until later. Too bad.
Why not? That’s how the navmesh agent is supposed to work. It can calculate the optimal path in a single frame. Have you determined that the navmesh agent breaks down at high velocities?
By the way, it’s possible to use the navmesh agent to calculate a path and then move the character yourself using whatever method you want. Then you can directly control how much the character moves.
You just have to set the navmesh’s updatePosition and updateRotation to false.
I found some code in my ATGM game that handled agents getting blown off course by explosions, but not getting killed, just returning to their previous pathing.
It uses NavMeshAgent.Warp() and it respects the walls and pathing… mostly!
I ripped it out of ATGM, wrapped it in a little demo package attached it here. Enjoy!
Ok, cool. So it’s very much a lunge forward, whether instantly or over time. But in either case it passes control back to Unity’s agent to resume navigation between frames. Thank you again.
Check my code there closely for how that happens… I found that calling Warp() actually cancels the navigation, which is why I call .SetDestination() again… I’ll drop the whole script here for others who might find this.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
// @kurtdekker - making a NavmeshAgent lunge forward.
public class LungingAgent : MonoBehaviour
{
NavMeshAgent agent;
const float NominalLungeSpeed = 10;
float LungeSpeed;
// TWO ways to lunge:
// set the timer for how long to lunge
float LungeTimer;
// set the distance for how FAR to lunge
float LungeDistance;
Vector3 LungeDirection;
Vector3 LastCommandedDestination;
public Transform DestinationMarker;
void Start ()
{
agent = GetComponent<NavMeshAgent>();
SetDestination( agent.transform.position);
}
void SetDestination( Vector3 dest)
{
LastCommandedDestination = dest;
agent.SetDestination( dest);
}
void UpdateNewDestinations()
{
if (Input.GetMouseButtonDown(0))
{
var ray = Camera.main.ScreenPointToRay( Input.mousePosition);
var plane = new Plane( inNormal: Vector3.up, inPoint: Vector3.zero);
float enter = 0.0f;
if (plane.Raycast( ray, out enter))
{
LastCommandedDestination = ray.GetPoint( enter);
SetDestination( LastCommandedDestination);
}
}
}
void UpdateLunging()
{
{
bool isLunging = false;
bool resumeOwnNavigation = false;
if (LungeTimer > 0)
{
isLunging = true;
LungeTimer -= Time.deltaTime;
if (LungeTimer <= 0)
{
resumeOwnNavigation = true;
}
}
if (LungeDistance > 0)
{
isLunging = true;
float stepDistance = Mathf.Abs( LungeSpeed * Time.deltaTime);
LungeDistance -= stepDistance;
if (LungeDistance <= 0)
{
resumeOwnNavigation = true;
}
}
if (resumeOwnNavigation)
{
// when the lunge ends, resume own navigation
SetDestination( LastCommandedDestination);
return;
}
if (isLunging)
{
Vector3 aheadPosition = agent.transform.position;
aheadPosition += LungeDirection.normalized * LungeSpeed * Time.deltaTime;
agent.Warp( aheadPosition);
}
}
// can't interrupt a lunge with a lunge... that would be madness!
if (LungeTimer <= 0 && LungeDistance <= 0)
{
if (Input.GetKeyDown( KeyCode.K))
{
LungeTimer = 0.5f;
LungeSpeed = NominalLungeSpeed;
LungeDirection = LastCommandedDestination - agent.transform.position;
}
if (Input.GetKeyDown( KeyCode.L))
{
LungeTimer = 0.5f;
LungeSpeed = NominalLungeSpeed;
LungeDirection = agent.transform.forward;
}
if (Input.GetKeyDown( KeyCode.B))
{
LungeDistance = 1.0f;
LungeSpeed = -NominalLungeSpeed;
LungeDirection = agent.transform.forward;
}
}
}
void UpdateDestinationMarker()
{
Vector3 position = DestinationMarker.position;
position = Vector3.MoveTowards( position, LastCommandedDestination, 100 * Time.deltaTime);
DestinationMarker.position = position;
}
void Update ()
{
UpdateLunging();
UpdateNewDestinations();
UpdateDestinationMarker();
}
}