
- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала

58 Глава 2. Создание 3D-ролика
После запуска этого сценария вы получите возможность смотреть во всех направлениях, просто перемещая указатель мыши. Великолепно! Но вы все еще стоите на месте, как орудийная башня. В следующем разделе игрок начнет перемещаться по сцене.
2.5. Компонент для клавиатурного ввода
Возможность смотреть по сторонам в ответ на перемещения указателя мыши относится к важным фрагментам элементов управления персонажем в играх от первого лица, но это только половина дела. Кроме этого, игрок должен перемещаться по сцене в ответ на клавиатурный ввод. Поэтому в дополнение к компоненту для элемента управления мышью напишем компонент для клавиатурного ввода; создайте новый компонент C# script с именем FPSInput и присоедините его к игроку (в дополнение к сценарию MouseLook). При этом мы на время ограничим компонент MouseLook только горизонтальным вращением.
СОВЕТ Рассматриваемые нами элементы управления клавиатурным вводом и вводом с помощью мыши помещены в отдельные сценарии. Вы вовсе не обязаны структурировать код подобным образом и вполне можете поместить все в единый сценарий «элементов управления игроком». Но, как правило, модульные системы (такие, как в Unity) стремятся к максимальной гибкости, и, соответственно, эффективнее всего моделировать функциональность как набор отдельных небольших компонентов.
Код, который мы писали, влиял только на ориентацию объекта, теперь же мы будем менять только его местоположение. Как показано в листинге 2.8, мы берем код вращения и вводим его в сценарий FPSInput, заменяя метод Rotate() методом Translate(). После щелчка на кнопке Play сцена начнет скользить вверх, а не вращаться, как раньше. Попытайтесь поменять значения параметров и посмотреть, как изменится движение (например, после того как вы поменяете местами первое и второе числа); после некоторого количества экспериментов можно будет перейти к добавлению клавиатурного ввода.
Листинг 2.8. Код вращения из первого листинга с парой небольших изменений
using UnityEngine;
using System.Collections;
public class FPSInput : MonoBehaviour {
public float speed = 6.0f; ¬ Необязательный элемент на случай, если вы захотите увеличить скорость. void Update() {
transform.Translate(0, speed, 0); ¬ Меняем метод Rotate() на метод Translate()
}
}
2.5.1. Реакция на нажатие клавиш
Код перемещения в ответ на нажатия клавиш (показанный в листинге 2.9) аналогичен коду вращения в ответ на движение указателя мыши. Мы снова используем метод GetAxis(), причем аналогичным образом.

2.5. Компонент для клавиатурного ввода 59
Листинг 2.9. Изменение положения в ответ на нажатие клавиш
...
void Update() {
float deltaX = Input.GetAxis("Horizontal") * speed; ¬ float deltaZ = Input.GetAxis("Vertical") * speed; transform.Translate(deltaX, 0, deltaZ);
}
"Horizontal" и "Vertical" — это дополнительные имена для сопоставления с клавиатурой.
Как и раньше, значения, задаваемые методом GetAxis(), умножаются на скорость, давая в итоге величину смещения. Но если раньше в ответ на запрос оси мы получали ответ «Mouse имя оси», то теперь это значения Horizontal или Vertical. Это абстрактные представления для параметров ввода в Unity; если в меню Edit навести указатель мыши на строку Project Settings и выбрать в открывшемся меню команду Input, откроется список абстрактных имен ввода и соответствующих им элементов управления. Клавиши с указывающими влево/вправо стрелками и с буквами A/D соответствуют имени Horizontal, в то время как клавиши со стрелками, указывающими вверх/вниз, и с буквами W/S соответствуют имени Vertical.
Обратите внимание, что значения перемещения меняются по координатам X и Z. Вы могли заметить в процессе экспериментов с методом Translate(), что изменение координаты X соответствует движению из стороны в сторону, в то время как изменение координаты Z означает движение вперед и назад.
Вставив в сценарий этот новый код перемещения, вы получите возможность двигать объект по сцене нажатием клавиш со стрелками или клавиш с буквами WASD. Это стандартная ситуация для большинства игр от первого лица. Сценарий, управляющий перемещениями, практически готов, осталось только внести в него некоторые изменения.
2.5.2. Независимая от скорости работы компьютера скорость перемещений
Пока вы запускаете код только на собственном компьютере, этот аспект незаметен, но на разных машинах игрок будет перемещаться с разной скоростью. Ведь более мощные компьютеры быстрее обрабатывают код и графику. В настоящее время скорость перемещения вашего игрока привязана к скорости работы компьютера. Это называется зависимостью от кадровой частоты (frame rate dependent), так как код движения зависит от частоты кадров игры.
Например, представьте, что вы запускаете деморолик на двух компьютерах, один из которых отображает на мониторе 30 кадров в секунду, а второй 60. Это означает, что на втором компьютере метод Update() будет вызываться в два раза чаще, двигая объект с одной и той же скоростью 6 при каждом вызове. В итоге при частоте 30 кадров в секунду скорость движения составит 180 единиц в секунду, в то время как частота 60 кадров в секунду обеспечит скорость перемещения в 360 единиц в секунду. Такая вариативность неприемлема для большинства игр.
Решить эту проблему позволяет такое редактирование кода, в результате которого мы получим независимость от кадровой частоты (frame rate independent). То есть

60 Глава 2. Создание 3D-ролика
скорость перемещения перестанет зависеть от частоты кадров игры. Это обеспечивается сменой скорости в соответствии с частотой кадров. Она должна уменьшаться или увеличиваться в зависимости от того, насколько быстро работает компьютер. Достигнуть такого результата нам поможет умножение скорости на переменную deltaTime, как показано в следующем листинге.
Листинг 2.10. Движение с независимостью от кадровой частоты благодаря переменной deltaTime
...
void Update() {
float deltaX = Input.GetAxis("Horizontal") * speed; float deltaZ = Input.GetAxis("Vertical") * speed;
transform.Translate(deltaX * Time.deltaTime, 0, deltaZ * Time.deltaTime);
}
...
Это простое изменение. Класс Time обладает рядом свойств и методов, позволяющих регулировать время. К таким свойствам относится и deltaTime. Так как мы знаем, что дельта указывает на величину изменения, переменная deltaTime означает величину изменения во времени. А конкретно — это время между кадрами. Его величина зависит от частоты кадров (например, при частоте 30 кадров в секунду deltaTime составляет 1/30 секунды). Поэтому умножение скорости на эту переменную приведет к масштабированию скорости на различных компьютерах.
Теперь движение нашего персонажа одинаково на всех машинах. Но сценарий еще не завершен; ведь, перемещаясь по комнате, мы можем проходить сквозь стены.
2.5.3. Компонент CharacterController
для распознавания столкновений
Назначенные объекту напрямую преобразования не затрагивают такого аспекта, как распознавание столкновений. В результате персонаж начинает ходить сквозь стены. Поэтому нам требуется компонент CharacterController. Именно он обеспечит естественность перемещений нашего персонажа. Напомню, что в момент настройки игрока мы присоединяли этот компонент, поэтому теперь остается воспользоваться им в коде сценария FPSInput (листинг 2.11).
Листинг 2.11. Перемещение компонента CharacterController вместо компонента Transform
...
private CharacterController _charController; ¬ Переменная для ссылки на компонент CharacterController.
void Start() { |
Доступ к другим компонентам, |
_charController = GetComponent<CharacterController>(); ¬ |
|
} |
присоединенным к этому же объекту. |
|
|
void Update() { |
|
float deltaX = Input.GetAxis("Horizontal") * speed; |
|
float deltaZ = Input.GetAxis("Vertical") * speed; |
|
Vector3 movement = new Vector3(deltaX, 0, deltaZ); |
|

2.5. Компонент для клавиатурного ввода 61
|
movement = Vector3.ClampMagnitude(movement, speed); ¬ |
Ограничим движение по диагонали той же |
|
|
|
скоростью, что и движение параллельно осям. |
|
|
movement *= Time.deltaTime; |
|
Преобразуем вектор движения от локальных |
|
movement = transform.TransformDirection(movement); ¬ |
|
|
|
_charController.Move(movement); ¬ Заставим этот вектор перемещать |
к глобальным координатам. |
|
|
|
||
} |
компонент CharacterController. |
|
|
... |
|
|
В этом фрагменте кода появляется несколько новых концепций. Прежде всего, это переменная для ссылки на компонент CharacterController. Она просто создает локальную ссылку на объект (объект в коде — не путайте его с объектами сцены); ссылаться на этот экземпляр компонента могут разные сценарии. Изначально эта переменная пуста, поэтому, прежде чем ею пользоваться, следует указать объект, на который вы будете ссылаться с ее помощью. Именно здесь появляется метод GetComponent(); он возвращает другие компоненты, присоединенные к тому же объекту GameObject. Вместо передачи параметра в скобках мы воспользовались синтаксисом C# и определили тип в угловых скобках <>.
Теперь, когда у нас появилась ссылка на компонент CharacterController, можно вызвать для этого контроллера метод Move(). Мы передаем этому методу вектор тем же способом, которым код вращения в зависимости от указателя мыши использовал вектор для значений поворота. А аналогично тому, как мы ограничивали значения поворота, мы задействуем метод Vector3.ClampMagnitude(), чтобы ограничить модуль вектора скоростью движения; мы берем именно этот метод, потому что в противном случае движение по диагонали происходило бы быстрее движения вдоль координатных осей (нарисуйте катеты и гипотенузу прямоугольного треугольника).
Но вектор движения обладает еще одной особенностью, связанной с выбором системы координат. Вы уже сталкивались с ней при обсуждении преобразования поворота. Например, мы создадим вектор, перемещающий объект влево. Но может оказаться, что у игрока это направление не совпадает с направлением координатных осей. То есть мы говорим о локальном, а не о глобальном пространстве. Поэтому методу Move() следует передавать вектор движения, определенный в глобальном пространстве, а значит, нам требуется преобразование от локальных к глобальным координатам. Математически это крайне сложная операция, но, к счастью для нас, об этом позаботится Unity. Нам же достаточно вызвать метод TransformDirection(), чтобы, как следует из его названия, преобразовать направление.
ОПРЕДЕЛЕНИЕ Слово «transform», используемое в качестве глагола, означает преобразование от одного координатного пространства к другому (если вы не помните, что такое координатное пространство, перечитайте раздел 2.3.3). Не следует путать его с аналогичным существительным, которое означает как компонент Transform, так и изменение положения объектов в сцене. В данном случае значения терминов перекрываются, так как в основе лежит одна и та же концепция.
Запустите код. Если это еще не сделано, установите для компонента MouseLook вращение одновременно по горизонтали и вертикали. Вы можете неограниченно осматривать пространство вокруг себя и летать, нажимая соответствующие клавиши. Это здорово, если вам требуется игрок, умеющий летать. Но как сделать, чтобы он перемещался исключительно по земле?