- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала
168 Глава 7. Игра от третьего лица: перемещение и анимация игрока
ПРИМЕЧАНИЕ Наиболее математически одаренные читатели могут подумать: «Нельзя ли воспользоваться преобразованиями от одной координатной системы к другой, о которых шла речь в главе 2?» Разумеется, мы можем преобразовать положение точки смещения, воспользовавшись углами Эйлера, но для этого сначала нужно будет перейти к другой системе координат. Намного проще без этого обойтись.
Завершает код метод LookAt(), направляющий камеру на целевой объект; он предназначен для нацеливания одного объекта (не обязательно камеры) на другой. Вычисленная ранее величина поворота используется для размещения камеры под нужным углом к целевому объекту, но при этом камера уже не поворачивается, меняется только ее положение. То есть без заключительной строки с методом LookAt камера летала бы вокруг персонажа, но при этом смотрела бы в разные стороны. Попробуйте превратить эту строку в комментарий и посмотреть, что получится.
Итак, мы написали сценарий, перемещающий камеру вокруг персонажа; пришла очередь кода, перемещающего персонаж по сцене.
7.2. Элементы управления движением, связанные с камерой
Теперь, когда мы импортировали в Unity модель персонажа и написали код, управляющий видом с камеры, пришло время создать элементы управления перемещением персонажа по сцене. Запрограммируем элементы управления, связанные с камерой, которые будут перемещать персонажа в разных направлениях при нажатии клавиш со стрелками, а также поворачивать его лицом в нужную сторону.
ЧТО ОЗНАЧАЕТ «СВЯЗАННЫЕ С КАМЕРОЙ»?
Принцип «связи с камерой» не вполне очевиден, но понять его крайне важно. Во многом он напоминает уже знакомую вам ситуацию с локальными и глобальными координатами, когда «лево»
сточки зрения объекта может не совпадать с «лево» в общем смысле. Аналогичная ситуация возникает с выражением «персонаж бежит налево». Оно может подразумевать как левую сторону
сточки зрения персонажа, так и левую сторону экрана.
В игре от первого лица камера располагается внутри персонажа и перемещается вместе с ним, поэтому понятие «лево» с точки зрения камеры и персонажа одно и то же. А вот в игре от третьего лица камера и персонаж существуют отдельно друг от друга, поэтому, к примеру, в ситуации, когда камера смотрит персонажу в лицо, лево с точки зрения камеры и с точки зрения персонажа располагается в противоположных направлениях. Именно поэтому нужно заранее выбрать настройки элементов управления.
Можно встретить разные варианты, но, как правило, в играх от третьего лица элементы управления настраиваются относительно камеры. При нажатии клавиши с левой стрелкой персонаж бежит по экрану налево. Многочисленные эксперименты сдругими схемами элементов управления показали, что интуитивно понятным является именно вариант, когда «лево» означает «левую сторону экрана» (что далеко не всегда совпадает с левой стороной игрока).
Реализация связанных с камерой элементов управления состоит из двух этапов: сначала мы ориентируем персонажа в соответствии с положением элементов управления, а затем двигаем его вперед. Давайте напишем соответствующий код.
7.2.Элементы управления движением, связанные с камерой 169
7.2.1.Поворот персонажа лицом в направлении движения
Начнем мы с написания кода, поворачивающего персонажа лицом в направлении клавиш со стрелками. Создайте сценарий RelativeMovement на C# (листинг 7.2). Перетащите этот сценарий на объект player и свяжите камеру со свойством Target компонента Script (таким же образом, как вы связывали персонажа со свойством Target сценария камеры). После этого персонаж при нажатии управляющих клавиш будет поворачиваться в разные стороны, выбирая направление относительно камеры, а при его вращении с помощью мыши он останется статичным.
Листинг 7.2. Поворот персонажа относительно камеры
using UnityEngine;
using System.Collections;
public class RelativeMovement : MonoBehaviour {
[SerializeField] private Transform target; ¬ Сценарию нужна ссылка на объект, относительно которого будет происходить перемещение.
void Update() {
Vector3 movement |
= Vector3.zero; ¬ Начинаем с вектора (0, 0, 0), непрерывно добавляя компоненты движения. |
||
float |
horInput = |
Input.GetAxis("Horizontal"); |
|
float |
vertInput |
= |
Input.GetAxis("Vertical"); |
if (horInput != |
0 |
|| vertInput != 0) { ¬ Движение обрабатывается только |
movement.x = horInput; |
при нажатии клавиш со стрелками. |
movement.z = vertInput; |
|
|
Сохраняем начальную ориентацию, чтобы вернуться |
Quaternion tmp = target.rotation; ¬ к ней после завершения работы с целевым объектом. |
|
target.eulerAngles = new Vector3(0, target.eulerAngles.y, 0); |
|
movement = target.TransformDirection(movement); ¬ Преобразуем направление движения |
|
target.rotation = tmp; |
из локальных в глобальные координаты. |
|
Метод LookRotation() вычисляет |
transform.rotation = Quaternion.LookRotation(movement); ¬ кватернион, смотрящий в этом |
|
} |
направлении. |
}
}
Аналогично коду листинга 7.1, код этого листинга начинается с сериализованной переменной для целевого объекта. Если в предыдущем случае нам требовалась ссылка на объект, вокруг которого будет совершаться облет, то теперь нужна ссылка на объект, относительно которого будет выполняться перемещение. Затем мы переходим к методу Update(). Его первая строка объявляет, что Vector3 имеет значение 0, 0, 0. Важно взять нулевой вектор и затем присвоить ему значения, а не просто создать вектор с вычисленными значениями перемещения. Дело в том, что величины перемещения по вертикали и горизонтали вычисляются на разных этапах, но при этом должны быть частями одного и того же вектора.
Далее мы проверяем элементы управления вводом, как делали это в предыдущих сценариях. Именно здесь вектору движения присваиваются значения координат X и Z, определяющие горизонтальные перемещения по сцене. Помните, что метод Input. GetAxis() возвращает значение 0, если ни одна клавиша не нажата, а при нажатии клавиш со стрелками его значение меняется от 1 до –1. Присваивание этого значения
170 Глава 7. Игра от третьего лица: перемещение и анимация игрока
вектору движения задает перемещение в положительном или отрицательном направлении оси (в случае оси X это перемещение влево и вправо, в случае оси Z — вперед и назад).
В следующих нескольких строках вектор движения корректируется относительно камеры. А именно метод TransformDirection() выполняет преобразование локальных координат в глобальные. Мы уже пользовались им для этой цели в главе 2, но на этот раз мы совершаем переход от системы координат целевого объекта, а не игрока. При этом код до и после строки с методом TransformDirection() выравнивает систему координат нужным нам образом: первым делом сохраняется ориентация целевого объекта, чтобы потом к ней можно было вернуться, а затем преобразование поворота корректируется таким образом, чтобы оно совершалось только относительно оси Y, а не всех трех осей. После чего мы выполняем преобразование и восстанавливаем ориентацию целевого объекта.
Весь этот код вычисляет направление движения в виде вектора. Последняя строка применяет полученный результат к персонажу, преобразуя Vector3 в Quaternion методом Quaternion.LookDirection() и присваивая это значение. Попробуйте запустить игру и посмотреть, что получится!
ПЛАВНОЕ ВРАЩЕНИЕ С ПОМОЩЬЮ АЛГОРИТМА ЛИНЕЙНОЙ ИНТЕРПОЛЯЦИИ
В настоящее время наш персонаж меняет ориентацию скачками, мгновенно поворачиваясь лицом в направлении, заданном клавишей. Но плавное движение смотрелось бы куда лучше. В этом нам поможет линейный алгоритм интерполяции. Добавьте в сценарий такую переменную:
public float rotSpeed = 15.0f;
Затем замените строку transform.rotation... в конце листинга 7.2 следующим кодом:
...
Quaternion direction = Quaternion.LookRotation(movement); transform.rotation = Quaternion.Lerp(transform.rotation, direction, rotSpeed * Time.deltaTime);
}
}
}
В результате вместо непосредственной привязки к значению, возвращаемому методом LookRotation(), это значение начнет использоваться в качестве целевого положения, в которое объект будет постепенно поворачиваться. Метод Quaternion.Lerp() выполняет плавный поворот из текущего положения в целевое (третий параметр метода контролирует скорость вращения).
Кстати, плавный переход от одного значения к другому называется интерполяцией; интерполировать можно значения любых типов. Термин Lerp расшифровывается как linear interpolation — линейная интерполяция. В Unity есть методы Lerp как для векторов, так и для значений типа float (то есть вы можете интерполировать положение объектов, цвета и другие параметры). Для кватернионов имеется также родственный метод Slerp (метод сферической линейной интерполяции). Для поворотов на маленькие углы преобразования Slerp зачастую подходят лучше, чем Lerp.
Пока что персонаж умеет только поворачиваться на месте; в следующем разделе мы напишем код, который заставит его перемещаться по сцене.
7.2. Элементы управления движением, связанные с камерой 171
ПРИМЕЧАНИЕ Так как поворотом налево и направо управляют клавиши, отвечающие за облет камеры, поворот персонажа в сторону будет сопровождаться медленным вращением. Подобное дублирование функций элементов управления является желательным поведением для данного проекта.
7.2.2. Движение вперед в выбранном направлении
Как вы помните из главы 2, для перемещения персонажа по сцене к нему нужно добавить соответствующий контроллер. Выделите персонажа и выберите в меню
Components команду Physics Character Controller. На панели Inspector уменьшите параметр Radius до 0.4, остальным же параметрам оставьте значения, предлагаемые по умолчанию, так как они вполне подходят для нашей модели персонажа.
Следующий листинг содержит код, который нужно добавить в сценарий Relative Movement.
Листинг 7.3. Код, меняющий положение персонажа
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(CharacterController))] ¬ |
Окружающие строки показывают контекст |
public class RelativeMovement : MonoBehaviour { |
размещения метода RequireComponent(). |
|
|
... |
|
public float moveSpeed = 6.0f; |
|
private CharacterController _charController; |
|
void Start() { |
Этот паттерн, знакомый вам |
_charController = GetComponent<CharacterController>(); ¬ по предыдущим главам, используется
} |
|
|
для доступа к другим компонентам. |
|
void Update() { |
|
|
|
|
... |
|
Переписываем существующие строки для X и Z, |
||
movement.x = horInput * moveSpeed; ¬ |
||||
movement.z = vertInput * moveSpeed; |
чтобы добавить скорость движения. |
|||
|
Ограничиваем движение |
|||
movement = Vector3.ClampMagnitude(movement, moveSpeed); |
||||
¬ по диагонали той же скоростью, |
||||
... |
|
|
что и движение вдоль оси. |
|
} |
|
|
|
|
movement *= Time.deltaTime; ¬ |
Не забывайте умножать перемещения |
|
||
на значение deltaTime, чтобы они |
|
_charController.Move(movement); не зависели от частоты кадров.
}
}
Запустив игру, вы увидите, как персонаж (в T-образной позе) перемещается по сцене. Практически весь код этого листинга вы уже видели, поэтому я ограничусь кратким обзором.
В первую очередь, в верхней части кода появился метод RequireComponent(). Как объяснялось в главе 2, этот метод заставляет Unity проверять наличие у объекта GameObject компонента переданного в команду типа. Хотя эта строка не обязательна, без указанного компонента в сценарии появятся ошибки.