- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 1. Знакомство с Unity
- •1.1. Достоинства Unity
- •1.1.1. Сильные стороны и преимущества Unity
- •1.1.2. Недостатки, о которых нужно знать
- •1.1.3. Примеры игр на основе Unity
- •1.2. Как работать с Unity
- •1.2.1. Вкладка Scene, вкладка Game и панель инструментов
- •1.2.2. Работа с мышью и клавиатурой
- •1.2.3. Вкладка Hierarchy и панель Inspector
- •1.2.4. Вкладки Project и Console
- •1.3. Готовимся программировать в Unity
- •1.3.1. Запуск кода в Unity: компоненты сценария
- •1.3.2. Программа MonoDevelop — межплатформенная среда разработки
- •1.4. Заключение
- •Глава 2. Создание 3D-ролика
- •2.1. Подготовка…
- •2.1.1. Планирование проекта
- •2.1.2. Трехмерное координатное пространство
- •2.2. Начало проекта: размещение объектов
- •2.2.1. Декорации: пол, внешние и внутренние стены
- •2.2.2. Источники света и камеры
- •2.2.3. Коллайдер и точка наблюдения игрока
- •2.3. Двигаем объекты: сценарий, активирующий преобразования
- •2.3.1. Схема программирования движения
- •2.3.2. Написание кода
- •2.3.3. Локальные и глобальные координаты
- •2.4. Компонент сценария для осмотра сцены: MouseLook
- •2.4.1. Горизонтальное вращение, следящее за указателем мыши
- •2.4.2. Поворот по вертикали с ограничениями
- •2.4.3. Одновременные горизонтальное и вертикальное вращения
- •2.5. Компонент для клавиатурного ввода
- •2.5.1. Реакция на нажатие клавиш
- •2.5.2. Независимая от скорости работы компьютера скорость перемещений
- •2.5.4. Ходить, а не летать
- •2.6. Заключение
- •3.1. Стрельба путем бросания лучей
- •3.1.1. Что такое бросание лучей?
- •3.1.2. Имитация стрельбы командой ScreenPointToRay
- •3.1.3. Добавление визуальных индикаторов для прицеливания и попаданий
- •3.2. Создаем активные цели
- •3.2.1. Определяем точку попадания
- •3.2.2. Уведомляем цель о попадании
- •3.3. Базовый искусственный интеллект для перемещения по сцене
- •3.3.1. Диаграмма работы базового искусственного интеллекта
- •3.3.2. «Поиск» препятствий методом бросания лучей
- •3.3.3. Слежение за состоянием персонажа
- •3.4.1. Что такое шаблон экземпляров?
- •3.4.2. Создание шаблона врага
- •3.4.3. Экземпляры невидимого компонента SceneController
- •3.5. Стрельба путем создания экземпляров
- •3.5.1. Шаблон снаряда
- •3.5.2. Стрельба и столкновение с целью
- •3.5.3. Повреждение игрока
- •3.6. Заключение
- •Глава 4. Работа с графикой
- •4.1. Основные сведения о графических ресурсах
- •4.2. Создание геометрической модели сцены
- •4.2.1. Назначение геометрической модели
- •4.2.2. Рисуем план уровня
- •4.2.3. Расставляем примитивы в соответствии с планом
- •4.3. Наложение текстур
- •4.3.1. Выбор формата файла
- •4.3.2. Импорт файла изображения
- •4.3.3. Назначение текстуры
- •4.4. Создание неба с помощью текстур
- •4.4.1. Что такое скайбокс?
- •4.4.2. Создание нового материала для скайбокса
- •4.5. Собственные трехмерные модели
- •4.5.1. Выбор формата файла
- •4.5.2. Экспорт и импорт модели
- •4.6. Системы частиц
- •4.6.1. Редактирование параметров эффекта
- •4.6.2. Новая текстура для пламени
- •4.6.3. Присоединение эффектов частиц к трехмерным объектам
- •4.7. Заключение
- •5.1. Подготовка к работе с двухмерной графикой
- •5.1.1. Подготовка проекта
- •5.1.2. Отображение двухмерных изображений (спрайтов)
- •5.1.3. Переключение камеры в режим 2D
- •5.2. Создание карт и превращение их в интерактивные объекты
- •5.2.1. Создание объекта из спрайтов
- •5.2.2. Код ввода с помощью мыши
- •5.2.3. Открытие карты по щелчку
- •5.3. Отображение различных карт
- •5.3.1. Программная загрузка изображений
- •5.3.3. Создание экземпляров карт
- •5.3.4. Тасуем карты
- •5.4. Совпадения и подсчет очков
- •5.4.1. Сохранение и сравнение открытых карт
- •5.4.2. Скрытие несовпадающих карт
- •5.4.3. Текстовое отображение счета
- •5.5. Кнопка Restart
- •5.5.1. Добавление к компоненту UIButton метода SendMessage
- •5.5.2. Вызов метода LoadLevel в сценарии SceneController
- •5.6. Заключение
- •Глава 6. Двухмерный GUI для трехмерной игры
- •6.1. Перед тем как писать код…
- •6.1.1. IMGUI или усовершенствованный 2D-интерфейс?
- •6.1.2. Выбор компоновки
- •6.1.3. Импорт изображений UI
- •6.2. Настройка GUI
- •6.2.1. Холст для интерфейса
- •6.2.2. Кнопки, изображения и текстовые подписи
- •6.2.3. Управление положением элементов UI
- •6.3. Программирование интерактивного UI
- •6.3.1. Программирование невидимого объекта UIController
- •6.3.2. Создание всплывающего окна
- •6.3.3. Задание значений с помощью ползунка и поля ввода
- •6.4. Обновление игры в ответ на события
- •6.4.1. Интегрирование системы сообщений
- •6.4.2. Рассылка и слушание сообщений сцены
- •6.4.3. Рассылка и слушание сообщений проекционного дисплея
- •6.5. Заключение
- •7.1. Корректировка положения камеры
- •7.1.1. Импорт персонажа
- •7.1.2. Добавление в сцену теней
- •7.1.3. Облет камеры вокруг персонажа
- •7.2. Элементы управления движением, связанные с камерой
- •7.2.1. Поворот персонажа лицом в направлении движения
- •7.2.2. Движение вперед в выбранном направлении
- •7.3. Выполнение прыжков
- •7.3.1. Добавление вертикальной скорости и ускорения
- •7.3.2. Распознавание поверхности с учетом краев и склонов
- •7.4. Анимация персонажа
- •7.4.1. Создание анимационных клипов для импортированной модели
- •7.4.2. Создание контроллера для анимационных клипов
- •7.4.3. Код, управляющий контроллером-аниматором
- •7.5. Заключение
- •8.1. Создание дверей и других устройств
- •8.1.1. Открывание и закрывание дверей
- •8.1.2. Проверка расстояния и направления перед открытием двери
- •8.1.3. Управление меняющим цвет монитором
- •8.2. Взаимодействие с объектами путем столкновений
- •8.2.1. Столкновение с препятствиями, обладающими физическими свойствами
- •8.2.2. Управление дверью с помощью триггера
- •8.2.3. Сбор разбросанных по игровому уровню элементов
- •8.3. Управление инвентаризационными данными и состоянием игры
- •8.3.1. Настраиваем диспетчеры игрока и инвентаря
- •8.3.2. Программирование диспетчеров
- •8.3.3. Сохранение инвентаря в виде коллекции: списки и словари
- •8.4. Интерфейс для использования и подготовки элементов
- •8.4.1. Отображение элементов инвентаря в UI
- •8.4.2. Подготовка ключа для открытия двери
- •8.4.3. Восстановление здоровья персонажа
- •8.5. Заключение
- •9.1. Создание натурной сцены
- •9.1.1. Генерирование неба с помощью скайбокса
- •9.1.2. Настройка управляемой кодом атмосферы
- •9.2. Скачивание сводки погоды из Интернета
- •9.2.1. Запрос веб-данных через сопрограмму
- •9.2.2. Парсинг текста в формате XML
- •9.2.3. Парсинг текста в формате JSON
- •9.2.4. Изменение вида сцены на базе данных о погоде
- •9.3. Добавление рекламного щита
- •9.3.1. Загрузка изображений из Интернета
- •9.3.2. Вывод изображения на щите
- •9.3.3. Кэширование скачанного изображения
- •9.4. Отправка данных на веб-сервер
- •9.4.1. Слежение за погодой: отправка запросов POST
- •9.4.2. Серверный код в PHP-сценарии
- •9.5. Заключение
- •Глава 10. Звуковые эффекты и музыка
- •10.1. Импорт звуковых эффектов
- •10.1.1. Поддерживаемые форматы файлов
- •10.1.2. Импорт аудиофайлов
- •10.2. Воспроизведение звуковых эффектов
- •10.2.1. Система воспроизведения: клипы, источник, подписчик
- •10.2.2. Присваивание зацикленного звука
- •10.2.3. Активация звуковых эффектов из кода
- •10.3. Интерфейс управления звуком
- •10.3.1. Настройка центрального диспетчера управления звуком
- •10.3.2. UI для управления громкостью
- •10.3.3. Воспроизведение звуков UI
- •10.4. Фоновая музыка
- •10.4.1. Воспроизведение музыкальных циклов
- •10.4.2. Отдельная регулировка громкости
- •10.4.3. Переход между песнями
- •10.5. Заключение
- •Глава 11. Объединение фрагментов в готовую игру
- •11.1. Построение ролевого боевика изменением назначения проектов
- •11.1.1. Сборка ресурсов и кода из разных проектов
- •11.1.2. Элементы наведения и щелчка
- •11.1.3. Замена старого GUI новым
- •11.2. Разработка общей игровой структуры
- •11.2.1. Управление ходом миссии и набором уровней
- •11.2.2. Завершение уровня
- •11.2.3. Проигрыш уровня
- •11.3. Обработка хода игры
- •11.3.1. Сохранение и загрузка достижений игрока
- •11.3.2. Победа в игре при прохождении всех уровней
- •11.4. Заключение
- •Глава 12. Развертывание игр на устройствах игроков
- •12.1. Создание приложений для настольных компьютеров: Windows, Mac и Linux
- •12.1.1. Построение приложения
- •12.1.2. Настройки проигрывателя: имя и значок приложения
- •12.1.3. Компиляция в зависимости от платформы
- •12.2. Создание игр для Интернета
- •12.2.1. Проигрыватель Unity и HTML5/WebGL
- •12.2.2. Создание файла Unity и тестовой веб-страницы
- •12.2.3. Обмен данными с JavaScript в браузере
- •12.3. Сборки для мобильных устройств: iOS и Android
- •12.3.1. Настройка инструментов сборки
- •12.3.2. Сжатие текстур
- •12.3.3. Разработка подключаемых модулей
- •12.4. Заключение
- •Приложение А. Перемещение по сцене и клавиатурные комбинации
- •А.1. Навигация с помощью мыши
- •А.2. Распространенные клавиатурные комбинации
- •Б.1. Инструменты программирования
- •Б.1.1. Visual Studio
- •Б.1.2. Xcode
- •Б.1.3. Android SDK
- •Б.1.4. SVN, Git или Mercurial
- •Б.2. Приложения для работы с трехмерной графикой
- •Б.2.1. Maya
- •Б.2.3. Blender
- •Б.3. Редакторы двухмерной графики
- •Б.3.1. Photoshop
- •Б.3.2. GIMP
- •Б.3.3. TexturePacker
- •Б.4. Звуковое программное обеспечение
- •Б.4.1. Pro Tools
- •Б.4.2. Audacity
- •Приложение В. Моделирование скамейки в программе Blender
- •В.1. Создание сеточной геометрии
- •В.2. Назначение материала
74 Глава 3. Добавляем в игру врагов и снаряды
3.3. Базовый искусственный интеллект для перемещения по сцене
Статичная цель не очень интересна, поэтому давайте напишем код, который заставит врагов перемещаться по сцене. Такой код является одним из простейших примеров искусственного интеллекта (Artificial Intelligence, AI). Этот термин относится к сущностям, поведение которых контролируется компьютером. В данном случае такой сущностью служит враг в нашей игре, но в реальной жизни это может быть, например, робот.
3.3.1. Диаграмма работы базового искусственного интеллекта
Существует множество подходов к реализации искусственного интеллекта (это одна из основных областей исследований ученых, работающих в области теории вычислительных машин и систем), но для наших целей подойдет достаточно простой вариант. По мере накопления опыта и усложнения создаваемых вами игр вы, скорее всего, захотите познакомиться с другими способами реализации AI. Но пока мы ограничимся процессом, показанным на рис. 3.5.
Ша 1:
Н
Ша 2: Б а а
а
Ша 3:
О •
Ша 4: В • а •а• а а,
• а а 1
Рис. 3.5. Базовый искусственный интеллект: циклический процесс движения вперед
собходом препятствий
Вкаждом кадре код искусственного интеллекта сканирует окружающее пространство, чтобы определить свое дальнейшее поведение. Если на пути появляется препятствие, враг меняет направление движения. При этом независимо от поворотов он неуклонно двигается вперед. В итоге враг будет в разных направлениях ходить по комнате, непрерывно перемещаясь вперед и поворачиваясь, чтобы не сталкиваться со стенами.
3.3. Базовый искусственный интеллект для перемещения по сцене 75
Сам код будет иметь знакомый вам вид, так как движение врагов вызывают те же команды, что и движение игрока. Кроме того, в коде вы снова увидите метод бросания луча, но уже в другом контексте.
3.3.2. «Поиск» препятствий методом бросания лучей
Как вы узнали во введении к данной главе, бросание луча представляет собой прием, позволяющий решать различные задачи трехмерного моделирования. Первым делом на ум приходит имитация выстрелов, но кроме того, этот прием позволяет сканировать окружающее пространство. А так как в данном случае нам требуется решить именно эту задачу, код испускания лучей попадет в наш код для AI.
Раньше мы создавали луч, который брал начало из камеры, так как именно она служит глазами игрока. На этот раз луч будет начинаться в месте расположения врага. В первом случае луч проходил через центр экрана, теперь же он будет распространяться перед персонажем, как показано на рис. 3.6. Затем аналогично тому, как код стрельбы использовал информацию из структуры RaycastHit, чтобы определить, поражена ли какая-нибудь цель и где она находится, код AI задействует эту же информацию, чтобы определить наличие препятствия по ходу движения и расстояние до этого препятствия.
Ва а а AI а
, а а .
Ва а а
, а
Рис. 3.6. Обнаружение препятствий методом бросания луча
Основным различием между лучом, бросаемым в случае выстрела, и лучом в коде AI является радиус распознаваемого пространства. При стрельбе мы обходились бесконечно тонким лучом, в то время как у луча для AI будет большое сечение. Соответственно, мы воспользуемся методом SphereCast() вместо метода Raycast(). Все дело в том, что пули имеют маленький размер, в то время как, проверяя наличие препятствий перед персонажем, мы должны учитывать ширину самого персонажа.
Создайте сценарий с именем WanderingAI и присоедините его к целевому объекту (вместе со сценарием ReactiveTarget). Введите в него код из следующего листинга. Запустите код, и вы увидите, как враг перемещается по комнате; при этом вы можете выстрелить в него, и он среагирует на попадание так же, как и раньше.
76 Глава 3. Добавляем в игру врагов и снаряды
Листинг 3.7. Базовый сценарий WanderingAI
using UnityEngine;
using System.Collections;
public class WanderingAI : MonoBehaviour {
public float speed = 3.0f; ¬ Значения для скорости движения и расстояния, с которого
public float obstacleRange = 5.0f; |
начинается реакция на препятствие. |
||
void Update() { |
|
Непрерывно движемся вперед в каждом |
|
transform.Translate(0, 0, speed * Time.deltaTime); ¬ |
|||
кадре, несмотря на повороты. |
Луч находится в том же положе-
Ray ray = new Ray(transform.position, transform.forward); ¬ нии и нацеливается в том же
RaycastHit hit; |
|
направлении, что и персонаж. |
|
if (Physics.SphereCast(ray, 0.75f, out hit)) { ¬ |
Бросаем луч с описанной |
||
if (hit.distance < obstacleRange) { |
|
вокруг него окружностью. |
|
Поворот с наполовину случайным |
|||
float angle = Random.Range(-110, 110); ¬ |
transform.Rotate(0, angle, 0); |
выбором нового направления. |
|
|
} |
|
} |
|
}
}
В листинге появилась пара новых переменных. Одна — для скорости движения врага,
авторая — для расстояния, на котором враг начинает реагировать на препятствие. Затем мы добавили внутрь метода Update() метод Translate(), обеспечив непрерывное движение вперед (в том числе воспользовавшись переменной deltaTime для движения, не зависящего от частоты кадров). Кроме того, в метод Update() помещен код бросания луча, во многом напоминающий написанный нами ранее сценарий поражения целей. В данном случае прием бросания луча применяется для осмотра сцены,
ане для стрельбы. Луч создается на базе положения врага и направления его движения, а не на базе камеры.
Как уже упоминалось, для расчета траектории луча применяется метод Physics. SphereCast(), в качестве параметра принимающий радиус окружности, в пределах которой будут распознаваться пересечения. Во всех же прочих отношениях он аналогичен методу Physics.Raycast(). Сходство наблюдается в способе получения информации о столкновении луча, способе проверки пересечений и применении свойства distance, гарантирующего, что враг среагирует только тогда, когда приблизится к препятствию.
Как только враг окажется перед стеной, код поменяет направление его движения наполовину случайным образом. Я использую словосочетание «наполовину случайным», так как значения ограничены минимумом и максимумом, имеющими смысл в данной ситуации. Мы прибегаем к методу Random.Range(), которым Unity позволяет получить случайное значение в указанном диапазоне. В нашем случае ограничения немного превышают величины поворота влево и вправо, что дает персонажу возможность развернуться на достаточный угол, чтобы избежать препятствия.
3.3.3. Слежение за состоянием персонажа
Текущее поведение врага имеет один недостаток. Движение вперед продолжается даже после попадания в него пули. Ведь метод Translate() запускается в каждом кадре вне зависимости от обстоятельств. Внесем в код небольшие изменения,
3.3. Базовый искусственный интеллект для перемещения по сцене 77
позволяющие следить за тем, жив персонаж или мертв. Говоря техническим языком, мы хотим отслеживать «живое» состояние персонажа. Код, по-разному реагирующий на разные состояния, представляет собой паттерн, распространенный во многих областях программирования, а не только в AI. Более сложные реализации этого паттерна называются конечными автоматами.
ОПРЕДЕЛЕНИЕ Конечным автоматом (Finite State Machine, FSM) называется структура кода, в которой отслеживается текущее состояние объекта, существуют четко определенные переходы между состояниями и код ведет себя по-разному в зависимости от состояния.
Разумеется, речь о настоящей реализации конечного автомата не идет, но нет ничего странного в том, что при обсуждении искусственного интеллекта упоминаются основы FSM. Конечный автомат обладает множеством состояний для различных вариантов поведения сложного искусственного интеллекта, в случае же базового искусственного интеллекта достаточно отследить, жив персонаж или уже нет. В следующем листинге в начальную часть сценария добавляется логическая переменная _alive, значение которой будет периодически проверяться в коде. Благодаря этим проверкам код движения запускается только для живого персонажа.
Листинг 3.8. Сценарий WanderingAI после добавления «живого» состояния
...
private bool _alive; ¬ Логическая переменная для слежения за состоянием персонажа.
void Start() {
_alive = true; ¬ Инициализация этой переменной.
}
void Update() {
if (_alive) { ¬ Движение начинается только в случае живого персонажа. transform.Translate(0, 0, speed * Time.deltaTime);
...
}
}
public void SetAlive(bool alive) { ¬ |
Открытый метод, позволяющий внешнему коду |
_alive = alive; |
воздействовать на «живое» состояние. |
|
|
} |
|
... |
|
Теперь сценарий ReactiveTarget может сообщить сценарию WanderingAI, в каком состоянии находится враг.
Листинг 3.9. Сценарий ReactiveTarget сообщает сценарию WanderingAI, когда наступает смерть
...
public void ReactToHit() {
WanderingAI behavior = GetComponent<WanderingAI>();
if (behavior != null) { ¬ Проверяем, присоединен ли к персонажу сценарий WanderingAI; он может и отсутствовать. behavior.SetAlive(false);
}
StartCoroutine(Die());
}
...