Unity3d Скрипт AI полетов по waypoint

Доброго времени суток!

В данной заметке хочу описать способ, позволяющий заставить летающие объекты двигаться по waypoint / вейпоинтам.

Данная заметка не претендует на идеальное решение, но является отправной точкой, которая позволит создать свой вариант алгоритма AI.

Для начала создам пустую сцену, создам 3 пустых объекта GameObject и один куб (превратим его в прямоугольник, чтобы были понятнее его манёвры).

3 пустых объекта — это наши вейпоинты. Их надо расположить в сцене как нравится (в поле зрения камеры) , «покрасить» и назвать. Размер куба я сделал таким x = 2, y=1, z=4. Движение нашего «летающего объекта» будет проходить по оси Z.

Добавляем, в наш куб, Rigidbody и ставим галочки UseGravity и IsKinetic, а так же там обязательно должен быть Box Collider. Всё это нам понадобиться для взаимодействия с внешним миром.

Приступаем к написанию скрипта C#. Я назвал его drone_ai, потому что писал для движения дронов. Открываем созданный файл и начинаем описывать нужные нам свойства и методы. Начнём со свойств:

public Transform[] waypoints; //список вейпоинтов
public float WingSpan = 10f; //размах крыльев
public float maxSpeed = 5f; //Скорость движение
public int SphereCastRadius = 10; // радиус сферы пересечения лучей

private Transform myTransform; // наш "куб"
private Vector3 tempPosition;

private int wpIndex = 0; // индекс текущего вейпоинта
private Transform target; // сам вейпоинт
private Vector3 storeTarget;
private Vector3 newTargetPos;
private bool savePos;
private bool overrideTarget;

private Vector3 acceleration;
private Vector3 velocity;
private float storeMaxSpeed;
private float targetSpeed;
private Transform storeMainTarget;

private Rigidbody rigidBody;

Много свойств не описаны в комментариях, они нужны как флаги или временные хранилища основных свойств. Теперь опишем метод Awake() и Start():

    void Awake()
    {
        myTransform = transform;
        tempPosition = transform.position;
    }

    // Use this for initialization
    void Start()
    {
        storeMaxSpeed = maxSpeed;
        targetSpeed = storeMaxSpeed;
        rigidBody = GetComponent<Rigidbody>();

        target = waypoints[wpIndex];
        storeMainTarget = target;
    }

Что тут произошло:

  1. Запомнили сами себя и поместили своё местоположение во вспомогательное «хранилище».
  2. Заполонили вспомогательные «хранилища», получили свой компонент Rigidbody.
  3. Получили первоначальную цель, т.е. первый вейпоинт и поместили его так же во вспомогательное «хранилище»

Дальше самое интересное — это метод Update(), но прежде чем рассматривать его, напишем функцию движения вперёд MoveTowardsTarget(Vector3 target), параметром является вейпоинт:

    Vector3 MoveTowardsTarget(Vector3 target){
        Vector3 distance = target - transform.position;

        if (distance.magnitude < 5){
            return distance.normalized * -maxSpeed;
        }else{
            return distance.normalized * maxSpeed;
        }
    }

Что тут происходит: Вычисляется расстояние от «нас(куб)» до вейпоинта и возвращается Vector3. Теперь рассмотрим метод Update():

void Update () {
    Vector3 forces = MoveTowardsTarget(target.position);
    acceleration = forces;
    velocity += 2 * acceleration * Time.deltaTime;

    if (velocity.magnitude > maxSpeed){
        velocity = velocity.normalized * maxSpeed;
    }

    rigidBody.velocity = velocity;

    Quaternion desiredRotation = Quaternion.LookRotation(velocity);
    transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, Time.deltaTime * 3);

    WaypointCatch(transform.forward, 0);

    if (overrideTarget){
       target.position = newTargetPos;
    }

    myTransform.position += myTransform.forward * maxSpeed * Time.deltaTime;
}

Зачем это всё: а это всё нам надо для поворота и придании скорости нашему «кубу» в сторону вейпоинта. Осталось рассмотреть процедуру определения «достижения» вейпоинта WaypointCatch():

void WaypointCatch(Vector3 direction, float offsetX){
        float distance = Vector3.Distance(transform.position, target.position);
        if (distance < WingSpan) { 
            wpIndex++;
            if (wpIndex >= waypoints.Length){
               wpIndex = 0;
            }
            target = waypoints[wpIndex];
        }
        overrideTarget = false;
}

В данной процедуре происходит проверка «долетели» ли до вейпоинта или нет, если «долетели» — тогда переключаемся на следующий вейпоинт. Переменная WingSpan — это «размах крыльев» нашего «куба», грубо говоря если расстояние до вейпоинта меньше размера «куба», значит «долетели».

Общий смысл всех манипуляций: «куб» должен «пролететь» через все вейпоинты. Его надо развернуть в сторону вейпоинта, придать скорость движения, определить «достигли ли вейпоинта», если достигли — значит надо сменить вейпоинт и заново повернуть в сторону нового вейпоинта, задать скорость движения и т.д.

Для демонстрации: добавим созданные вейпоинты в наш «куб», путём перетаскивания их в свойство «waypoints»

Конечно в статье не весь код скрипта и нельзя просто взять и всё скопировать, НО эти знания помогут найти отправную точку Вашей идее. Спасибо что прочли мою заметку.