,How to make the npc move smoothly instead of teleporting?

I have the zombie(npc) turn to a random direction and move a random number of units in that direction, but whatever I have tried so far just makes it teleport to that position instead of moving to it.

Here is the code:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class ZombieScript : MonoBehaviour
{

 public float speed = 1f;
 private float lookDirection;
 private float moveDistance;
 public Vector3 movement;
 public float timer = 0f;
 private float timerSpeed = 1f;
 public float timeToMove = 10f;
 public GameObject zombie;

 void Start()
 {
     lookDirection = Random.Range(-180f, 180f);
     moveDistance = Random.Range(5f, 10f);
 }

 void Update()
 {
     timer += Time.deltaTime * timerSpeed;
     if (timer >= timeToMove)
     {
         Vector3 newRotation = new Vector3(0, transform.rotation.y + lookDirection, 0);
         zombie.transform.eulerAngles = newRotation;
         Vector3 movement = Vector3.forward * moveDistance;
         zombie.transform.Translate(Vector3.forward * moveDistance);
         lookDirection = Random.Range(-180f, 180f);
         moveDistance = Random.Range(5f, 10f);
         timer = 0f;
     }
 }

}

Since your value for moveDistance is a relatively large distance for a single frame (> 0.1 units moved in ~16ms), this is why it appears to teleport. What we want to do instead is move it a very small fraction of this value over a number of frames, only stopping once it reaches the position it was previously teleporting to.

In your code, the character instantly moves by the distance defined in “moveDistance” whereas what we want to do is do this over a few seconds and therefore keep moving the character by moveDistance * Time.deltaTime * moveSpeed each frame until it’s close enough to the target position as defined by moveDistance instead. Note for this explanation I’ll mention the word “target” meaning the position that it’s moving towards, I DON’T mean an actual target!

So there are currently two things that happen in that update function of yours - we specify a new direction and a new distance, generally setting up the new movements AND we also do the movement itself. Let’s split these two up:

private void SetupNewMovement()
{
    lookDirection = Random.Range(-180f, 180f);
    Vector3 newRotation = new Vector3(0, transform.rotation.y + lookDirection, 0);
    zombie.transform.eulerAngles = newRotation;
    moveDistance = Random.Range(5f, 10f);
    targetPosition = transform.position + transform.forward * moveDistance;
    timer = 0f;
}

private void MoveZombie()
{
    Vector3 movement = Vector3.forward * moveSpeed * Time.deltaTime;
    zombie.transform.Translate(movement);
}

and finally, a new function for detecting whether we’re at our target yet:

private bool IsAtMovementTarget()
{
    // checks whether we are within 10cm from our target - an arbitrary "close to" value
    // this assumes that our movements will be less than 20cm each frame
    return Vector3.Distance(transform.position, targetPosition) < 0.1f; 
}

Note from these functions there are a couple more member we’ll need to define. “moveSpeed” can be public so you can set it to a sensible value in your inspector whereas “targetPosition” can be private as it will be handled entirely by the code.

Now most of this is your own code, with a few rearrangements. Let’s change the update function to call it in a slightly different manner so that we only change direction when we reach our target:

private void Update()
{
    if (IsAtMovementTarget())
    {
        SetupNewMovement();
    }
    MoveZombie();
}

If you take the content of these functions and put it back in the Update function you’ll find the result is quite similar to your original code but without the timer - since we’re moving over time instead. I hope the use of the named functions makes it easier to understand what is going on.

Extra reading:

If I was asked to write this behaviour I would either use coroutines or a state machine. Since it’s quite simple it’s okay to be solved like this but if you want to learn more about control methods for behaviours such as these, I’d look into either of these instead. Additionally, the IsAtMovementTarget function is a little weak - the assumption that our movement will be less than 2 times our “close enough” value each frame is annoying. We can solve this by checking that we are also moving towards our target: we can find the dot product of our forward direction vector and the vector from our position to our target. I’ll leave this up to you :slight_smile: