Хабрахабр

[Перевод] Создание простого ИИ на C# в Unity

image

Почти любой игре необходим взаимодействующий с пользователем искусственный интеллект (AI), чаще всего в виде враждебной игроку силы. В некоторых случаях ИИ должен помогать игроку, в других — бороться с ним, но у всех управляемых компьютером персонажей существует некоторое сходство. В зависимости от требований проекта ИИ может использовать простые или сложные поведения. Такими требованиями могут быть дипломатия с другим игроком или простое блуждание вперёд-назад по платформе. Как бы то ни было, необходимо сделать так, чтобы ИИ качественно выполнял свою работу.

Допустим, мы создаём игру, в которой игрок должен тайком пробраться рядом с вражеским штабом. В этом проекте я продемонстрирую очень простой искусственный интеллект. Именно это мы и реализуем в проекте на простейшем уровне. Когда игрока замечает камера слежения, рядом создаются враги и в течение короткого промежутка времени преследуют игрока. Завершив проект, вы получите управляемый объект игрока, круг, используемый в качестве камеры врага, и объект врага, который будет преследовать игрока, когда о его присутствии сообщит объект камеры.

Подготовка

Для начала нам нужно создать 3D-проект. Нажмите на кнопку New в верхней части окна после запуска Unity, как это показано на рисунке 1.

Рисунок 1: создание нового проекта

Выбрав место на компьютере для хранения проекта, нажмите на кнопку Create Project внизу, показанную на рисунке 2. Назовите свой проект AI и убедитесь, что он является 3D-проектом.

Рисунок 2: экран настройки проекта

Нажмите правой кнопкой на окне Assets и выберите Create → Folder для создания новой папки. После создания проекта нам первым делом нужно настроить папки в окне Assets, чтобы упорядочить свою работу. Затем создайте вторую папку и назовите её Scripts. Назовите эту папку Materials. На рисунке 3 показано, как это должно выглядеть.

Рисунок 3: создание новой папки

После всего этого окно Assets должно выглядеть так, как показано на рисунке 4.

Рисунок 4: окно Assets.

В окне Hierarchy выберите Create → 3D Object → Plane, чтобы создать объект-плоскость, которая будет использоваться в качестве пола. Далее создадим пол, на котором будут стоять все объекты.

Рисунок 5: создание объекта Plane.

После этого окно Inspector с выбранным объектом Floor должно выглядеть так, как показано на рисунке 6. Назовите этот объект Floor и измените его значение X Scale на 7, а значение Z Scale — на 3.

Рисунок 6: задание свойств объекта Floor.

В папке Materials окна Assets создайте новый материал, нажав правой кнопкой на окно Assets и выбрав Create → Material. Теперь нам нужно создать новый материал для Floor, чтобы отличать его от остальных объектов, которые будут размещены в сцене.

Рисунок 7: создание нового материала

После завершения назовите материал Floor.

Рисунок 8: материал Floor.

В верхней части окна Inspector с выбранным материалом Floor выберите color picker.

Рисунок 9: выбор color picker.

Разумеется, вы можете выбрать для пола любой цвет, но в этом примере я выбрал красно-коричневый цвет, как показано на рисунке 10.

Рисунок 10: color picker.

Выберите объект Floor в окне Hierarchy, и в компоненте Mesh Renderer выберите маленькую стрелку рядом с Materials.

Рисунок 11: подготовка к изменению материала.

Перетащите материал Floor из окна Assets в поле Element 0 компонента Mesh Renderer в окне Inspector.

Рисунок 12: задание материала Floor в качестве материала объекта Floor.

Снова заходим в Create → 3D Object → Plane для создания новой плоскости. Закончив с объектом Floor, мы дожны создать вокруг области стены, чтобы игрок не мог свалиться с края. Затем создадим ещё три стены, выбрав объект и трижды нажав Ctrl + D. Назовём эту плоскость Wall и выставим ей те же размеры, что и у Floor, то есть X Scale со значением 7 и Z Scale со значением 3. После этого разместим стены вокруг пола в соответствии с данными из таблицы.

Название

Position X

Position Y

Position Z

Rotation X

Rotation Z

Wall

-35

21

0

0

-90

Wall (1)

-1

11

-15

90

0

Wall (2)

-1

11

13.5

-90

0

Wall (3)

34

21

0

0

90

Таблица 1: позиции и повороты всех объектов Wall.

Выберите объект Main Camera и задайте для Y Position значение 30, для Z Position значение 0, а X Rotation — значение 80. Завершив всё это, нужно изменить положение камеры, чтобы она смотрела на пол сверху.

Рисунок 13: настройка объекта камеры.

В окне Hierarchy нажмите на Create → 3D Object → Sphere, чтобы создать объект-сферу. Сцена подготовлена, поэтому настало время создания персонажа игрока. Назовите этот объект Player, а затем нажмите на кнопку Add Component в нижней части окна Inspector.

Рисунок 14: добавление нового компонента.

После этого выберите из списка компонент Rigidbody и добавьте Rigidbody к объекту Player. Теперь найдите Rigidbody.

Рисунок 15: добавление компонента Rigidbody.

Нажмите на раскрывающееся меню Tag в левом верхнем углу окна Inspector и выберите тэг Player. Далее нужно присвоить игроку тэг, который позже пригодится нам в коде.

Рисунок 16: задание нового тэга.

В примере я расположил игрока в левом верхнем углу с X position равным 26, Y Position равным 1, и Z position равным -9. Нам нужно задать позицию игрока, чтобы он не находился под объектом Floor.

Рисунок 17: размещение игрока.

Снова заходим в окно Hierarchy и на этот раз выбираем Create → 3D Object → Cube. Чтобы наш будущий код работал правильно, нам, разумеется, нужно прикрепить его к объекту. Далее поместим его где-нибудь в верхнем левом углу сцены. Назовём этот куб Guard, добавим к нему компонент Rigidbody и компонент NavMesh Agent с помощью кнопки Add Component в окне Inspector. После этого окно Inspector объекта Guard будет выглядеть следующим образом:

Рисунок 18: объект Guard в окне Inspector.

И этот объект должен быть расположен так:

Рисунок 19: Размещение объекта Guard.

В последний раз перейдите в окно Hierarchy и выберите Create → 3D Object → Sphere для создания ещё одного объекта-сферы. Наконец, нам потребуется объект, используемый в качестве «глаз» объекта Guard, который будет уведомлять Guard о том, что его касается игрок. На этот раз нам не нужно добавлять к нему никаких других компонентов. Назовите этот объект Looker. Выбрав Looker, измените следующие переменные компонента Transform в окне Inspector. Однако мы изменим размер объекта.

  • Scale Xна 9.
  • Scale Y на 0.5.
  • Scale Z на 9.

После этого разместите объект Looker так, чтобы он располагался в средней верхней части пола, как показано на рисунке 20.

Рисунок 20: размещение объекта Looker.

В папке Materials окна Assets нажмите правой клавишей мыши и создайте новый материал. Настало подходящее время для того, чтобы придать Looker уникальный материал, чтобы было заметно, что его стоит избегать. После этого назначьте этот материал в качестве материала объекта Looker, чтобы изменить его цвет. Назовите его Looker и задайте ему ярко-красный цвет. После этого сцена должна выглядеть следующим образом:

Рисунок 21: объект Looker с новым материалом.

В верхней части редактора Unity есть меню Window. Единственное, что нам осталось — создать навигационный меш для Guard, по которому он сможет перемещаться. Выберите Window → Navigation, чтобы открыть окно Navigation, показанное на рисунке 22.

Рисунок 22: окно Navigation.

Выберите объект Floor в Hierarchy, а затем в окне Navigation поставьте флажок Navigation Static.

Рисунок 23: Navigation Static.

Далее выберем опцию Bake в верхней части окна.

Рисунок 24: переключение на меню Bake.

В нашем примере ничего изменять не требуется. Откроется меню Bake, в котором можно изменять свойства навигационного меша, который мы собираемся создать. Достаточно нажать на кнопку Bake в правой нижней части.

Рисунок 25: создание нового навигационного меша.

Сохраните её, после чего будет создан навигационный меш. На этом этапе Unity попросит сохранить сцену. Теперь сцена будет выглядеть так:

Рисунок 26: текущая сцена с добавленным навигационным мешем.

В окне Assets нажмите правой клавишей мыши и выберите Create → C# Script. Теперь всё в Unity настроено, поэтому настало время для создания скриптов, необходимых для работы проекта. Повторите эту операцию ещё два раза, создав скрипты с названиями Guard и Looker. Назовите этот скрипт Player.

Рисунок 27: создание нового скрипта.

После этого папка Scripts в окне Assets будет выглядеть так:

Рисунок 28: папка Scripts.

Дважды щёлкните по скрипту Player в окне Assets, чтобы открыть Visual Studio и приступить к созданию кода. Первым мы начнём писать код скрипта Player.

Код

Скрипт Player достаточно прост, всё что он делает — позволяет пользователю перемещать объект-мяч. Под объявлением класса нам нужно получить ссылку на компонент Rigidbody, который мы ранее создали в проекте.

private Rigidbody rb;

Сразу после этого в функции Start мы прикажем Unity сделать текущий компонент Rigidbody объекта Player значением rb.

rb = GetComponent<Rigidbody>();

После этого скрипт Player будет выглядеть так:

Рисунок 29: скрипт Player на текущий момент.

Для перемещения объекта мы будем использовать физику, применяя силу к объекту при нажатии пользователем клавиш со стрелками. Теперь, когда значение rb присвоено, нам нужно позволить объекту Player двигаться при нажатии клавиш со стрелками. Для этого достаточно добавить в функцию Update следующий код:

if (Input.GetKey(KeyCode.UpArrow)) rb.AddForce(Vector3.forward * 20);
if (Input.GetKey(KeyCode.DownArrow)) rb.AddForce(Vector3.back * 20);
if (Input.GetKey(KeyCode.LeftArrow)) rb.AddForce(Vector3.left * 20);
if (Input.GetKey(KeyCode.RightArrow)) rb.AddForce(Vector3.right * 20);

На этом мы завершили скрипт Player. Готовый скрипт будет выглядеть следующим образом:

Рисунок 30: готовый скрипт Player.

На этот раз выберите в окне Assets скрипт Guard. Сохраните свою работу и вернитесь в Unity. Чтобы заставить код для Guard работать, нужно добавить в верхнюю часть скрипта конструкцию using.

using UnityEngine.AI;
Next, declare the following variables just underneath the class declaration.
public GameObject player;
private NavMeshAgent navmesh;

В качестве значения переменной player объекта Guard используется объект Player. Она пригодится нам позже, когда мы прикажем объекту Guard преследовать игрока. Затем объявляется переменная navmesh для получения компонента NavMeshAgent объекта. Её мы используем позже, когда Guard начнёт преследовать игрока после того, как узнает о том, что игрок касается объекта Looker. В функции Start нам нужно задать в качестве значения переменной navmesh компонент NavMesh Agent объекта:

navmesh = GetComponent<NavMeshAgent>();

Затем в функции Update мы добавим единственную строку кода:

navmesh.destination = player.transform.position;

Эта строка задаёт точку назначения для объекта Guard. В нашем случае она будет брать текущую позицию объекта Player и перемещаться к этой точке. После срабатывания объект будет постоянно преследовать игрока. Вопрос в том, как выполняется процесс срабатывания? Он будет закодирован не в скрипте Guard, а в скрипте Looker. Прежде чем переходить к скрипту Looker, посмотрите на рисунок 31, чтобы сверить свой код скрипта Guard.

Рисунок 31: готовый скрипт Guard.

Внутри Looker нам снова нужно объявить следующие переменные:

public GameObject guard;
private float reset = 5;
private bool movingDown;

После этого закомментируем функцию Start, которая в этом скрипте нам не нужна. Перейдём к функции Update и добавим следующий код:

if (movingDown == false) transform.position -= new Vector3(0, 0, 0.1f); else transform.position += new Vector3(0, 0, 0.1f);
if (transform.position.z > 10) movingDown = false;
else if (transform.position.z < -10) movingDown = true;
reset -= Time.deltaTime;
if (reset < 0)
{ guard.GetComponent<Guard>().enabled = false; GetComponent<SphereCollider>().enabled = true;
}

Именно здесь происходят основные действия проекта, поэтому давайте проанализируем код. Во-первых, в зависимости от значения булевой переменной movingDown, объект, к которому прикреплён этот скрипт, будет двигаться вверх или вниз. Как только он достигнет определённой точки, то изменит направление. Далее Looker снизит значение сброса на основании реального времени. Как только таймер станет меньше нуля, он возьмём скрипт Guard из объекта Guard и отключит его, после чего объект Guard начнёт перемещаться к последней известной до этого моменрта позиции игрока, а затем остановится. Looker также снова включает его коллайдер, чтобы весь процесс мог начаться заново. Теперь наш скрипт выглядит следующим образом:

Рисунок 32: скрипт Looker.

В функции Update создайте следующий код: Кстати о коллайдерах: настало время создать код коллизии, сбрасывающий таймер Looker и включающий скрипт Guard.

private void OnCollisionEnter(Collision collision)

}

OnCollisionEnter Unity автоматически распознаёт как код коллизии, а поэтому выполняет его при возникновении коллизии с другим объектом. В нашем случае он сначала проверяет, имеет ли столкнувшийся объект тэг Player. Если нет, то он игнорирует остальную часть кода. В противном случае он включает скрипт Guard, задаёт таймеру reset значение 5 (то есть пять секунд), и отключает его коллайдер, чтобы игрок по-прежнему мог двигаться сквозь объект и случайно не застрял в объекте Looker. Функция показана на рисунке 33.

Рисунок 33: код коллизии для Looker.

Можно сделать ещё пару вещей, прежде чем закончить проект. На этом весь код проекта готов! Сохраните всю работу и вернитесь в Unity.

Завершение проекта

Для завершения проекта нам достаточно прикрепить скрипты к соответствующим объектам и задать несколько переменных. Во-первых, перейдите из окна Navigation в окно Inspector:

Рисунок 34: переход в окно Inspector.

Выберите его в окне Hierarchy, а затем в нижней части окна Inspector нажмите на кнопку Add Component и добавьте скрипт Player. После этого начнём с объекта Player. На этом объект Player завершён.

Рисунок 35: компонент скрипта Player.

Как и раньше, прикрепим скрипт Guard к объекту. Далее выберите объект Guard. Для этого перетащите объект Player из Hierarchy в поле Player компонента скрипта Guard, как показано на рисунке 36. На этот раз нам понадобится сообщить Guard, кто является игроком.

Рисунок 36: делаем объект Player значением поля Player.

В нашем проекте Guard будет преследовать игрока после включения его скрипта. Также нам нужно отключить скрипт Guard. Всё, что нужно сделать — снять флажок рядом с текстом Guard (Script) в компоненте: Этот скрипт Guard должен включаться только после того, как игрок коснётся объекта Looker.

Рисунок 37: отключение скрипта Guard.

На этот раз объекту Looker потребуется объект Guard в качестве значения его переменной Guard. Наконец, перейдём к объекту Looker и прикрепим к нему скрипт Looker. Перетащите Guard из Hierarchy в поле Guard скрипта Looker. Так же, как мы назначали объект Player переменной Player скрипта Guard, мы сделаем то же самое с объектом Guard и скриптом Looker. Нажмите на кнопку Play в верхней части редактора Unity, чтобы проверить свой проект. И на этом проект завершён!

Рисунок 38: тестирование проекта.

Заметьте, что после этого объект Guard начнёт преследовать игрока. Попробуйте переместить объект Player в объект Looker (не забывайте, что перемещение выполняется стрелками!). Он будет продолжать преследование примерно 5 секунд, после чего сдастся.

Рисунок 39: полностью готовый проект в действии.

Заключение

Этот ИИ очень прост, но его запросто можно расширить. Допустим, если мы представим, что объект Looker — это камера, а охранник смотрит через неё, чтобы найти вас, то будет логично дать объекту Guard собственную пару глаз. Игрок может проходить рядом с камерами, но они должны учитывать и глаза охранника. Также можно скомбинировать этот проект с концепцией поиска пути: дать охраннику путь, по которому он будет следовать, создав таким образом более интересную для игрока среду.

Возможно, вы не захотите делать ничего вышеизложенного и решите сделать что-то своё. Подобный простой ИИ можно развить множеством разных способов. Советую вам экспериментировать, возможно, у вас появится идея интересного проекта, который стоит довести до конца.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть