- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала
8.1. Создание дверей и других устройств 189
кода в стиле MVC (Model-View-Controller — Модель-Представление-Контроллер) для управления данными о собранных предметах инвентаря. Наконец, вы запрограммируете интерфейс, позволяющий пользоваться инвентарем для игры, к примеру, открывать дверь при помощи найденного ранее ключа.
ВНИМАНИЕ Предыдущие главы были относительно независимы друг от друга и с технической точки зрения не требовали проектов из более ранних глав, теперь же ряд листингов будет получен редактированием сценариев из главы 7. Если вы перешли сразу к этой главе, скачайте пример проекта для главы 7, чтобы у вас была основа для работы.
Пример проекта будет содержать эти устройства и элементы, случайным образом распределенные по игровому уровню. Для создания полноценной игры потребуется тщательно распланировать размещение элементов на игровом уровне, но мы пока всего лишь тестируем данную функциональность. Впрочем, случайное расположение объектов не помешает вам получить представление о порядке внедрения различных элементов.
Как обычно, я объясню вам все этапы построения кода, но если вы хотите сразу получить готовую версию, скачайте с сайта пример проекта.
8.1. Создание дверей и других устройств
Игровые уровни по большей части состоят из статичных стен и предметов обстановки, к которым добавляется изрядная доля функциональных устройств. Я имею в виду объекты, с которыми может взаимодействовать и которыми может пользоваться игрок. Это такие вещи, как включающийся свет или начинающий вращаться вентилятор. Многообразие таких устройств, по большому счету, ограничено только вашим воображением, но практически все они пользуются одинаковым кодом активации устройства игроком. В этой главе мы реализуем пару вариантов, что даст вам возможность адаптировать данный код к устройствам других видов.
8.1.1. Открывание и закрывание дверей
Первый вид устройства, который нам предстоит запрограммировать, — это открывающаяся и закрывающаяся дверь. Начнем мы с управления дверью путем нажатия клавиши. В игру можно поместить множество различных устройств с самыми разными способами управления. При этом дверь является одним из самых распространенных интерактивных устройств, а управление посредством клавиатуры — наиболее очевидным подходом, поэтому мы начнем именно с них.
В стенах есть несколько промежутков, в которые можно поместить новый объект, блокирующий проход. Я создал куб, в поля Position которого ввел значения 2.5, 1.5, 17, а в поля Scale — значения 5, 3, 0.5, получив показанную на рис. 8.1 дверь.
Создайте сценарий на C# с именем DoorOpenDevice и назначьте его объекту door. Его код, показанный в следующем листинге, заставит объект функционировать как дверь.
190 Глава 8. Добавление в игру интерактивных устройств и элементов
Рис. 8.1. Дверь, закрывающая проем в стене
Листинг 8.1. Сценарий, по команде закрывающий и открывающий дверь
using UnityEngine;
using System.Collections;
public class DoorOpenDevice : MonoBehaviour {
[SerializeField] private Vector3 dPos; ¬ Смещение, применяемое при открывании двери.
private bool _open; ¬ Переменная типа Boolean для слежения за открытым состоянием двери.
public void Operate() {
if (_open) { ¬ Открываем или закрываем дверь в зависимости от ее состояния.
Vector3 pos = transform.position - dPos; transform.position = pos;
}else {
Vector3 pos = transform.position + dPos; transform.position = pos;
}
_open = !_open;
}
}
Первая переменная определяет смещение, возникающее при открывании двери. Дверь сдвигается на указанное расстояние, когда ее открывают, а затем, чтобы закрыть дверь, это значение вычитается. Далее следует закрытая переменная логического типа, отслеживающая состояние двери. В методе Operate() выполняется преобразование объекта, меняющего его положение, путем добавления или вычитания величины смещения в зависимости от того, закрыта или открыта дверь; после этого состояние переменной _open меняется на противоположное.
Подобно другим сериализованным переменным, переменная dPos появляется на панели Inspector. Но так как она представляет собой структуру Vector3, вместо одного поля под одним именем переменной появятся три. Укажите относительное положение двери в открытом состоянии; я решил, что для этого она должна уходить вниз, поэтому смещение составило 0, –2.9, 0 (так как высота объекта door равна 3, смещение вниз на расстояние 2.9 оставит только небольшой порог, выступающий над полом).
Теперь нам нужен код, который должен вызывать функцию Operate(), открывающую и закрывающую дверь (обе ситуации будут обрабатываться одной и той же
8.1. Создание дверей и других устройств 191
функцией). Соответствующий сценарий для персонажа пока отсутствует; его написанием мы и займемся в следующем разделе.
ПРИМЕЧАНИЕ Преобразование совершается мгновенно, а вы, возможно, предпочитаете видеть, как открывается дверь. В главе 3 упоминалась такая методика, как анимация по начальной и конечной точкам, позволяющая получить плавное движение объектов. Английский термин tweening в разных контекстах имеет разные значения. В программировании игр он относится к командам кода, приводящим объекты в движение. Рекомендую обратить внимание на инструмент iTween, который можно скачать с официального сайта http://itween.pixelplacement.com.
8.1.2. Проверка расстояния и направления перед открытием двери
Создайте новый сценарий с именем DeviceOperator. Код из следующего листинга добавляет управляющую клавишу, которая воздействует на расположенные поблизости устройства.
Листинг 8.2. Клавиша контроля устройства для персонажа
using UnityEngine;
using System.Collections;
public class DeviceOperator : MonoBehaviour {
public float radius = 1.5f; ¬ Расстояние, с которого персонаж может активировать устройства.
void Update() {
if (Input.GetButtonDown("Fire3")) { ¬ Реакция на кнопку ввода, заданную в настройках ввода в Unity.
Collider[] hitColliders = |
|
|
Physics.OverlapSphere(transform.position, radius); ¬ Метод OverlapSphere() возвращает |
||
foreach (Collider hitCollider in hitColliders) { |
список ближайших объектов. |
|
|
||
hitCollider.SendMessage("Operate", |
Метод SendMessage() пытается вызвать |
|
SendMessageOptions.DontRequireReceiver); ¬ |
||
именованную функцию независимо от типа |
||
} |
целевого объекта. |
|
} |
|
|
} |
|
|
} |
|
Основная часть этого листинга вам уже знакома, но центральную часть кода занимает важный новый метод. Первым делом задается расстояние, на котором становится возможным управление устройством. После этого в методе Update() рассматривается клавиатурный ввод; так как клавиша прыжка уже задействована в сценарии RelativeMovement, на этот раз мы воспользуемся клавишей Fire3 (в настройках ввода проекта она определена как левая клавиша Command).
Вот мы и дошли до важного нового метода OverlapSphere(). Он возвращает массив всех объектов, расположенных не более чем на определенном расстоянии от текущего местоположения. Мы передаем в него положение персонажа и переменную radius, а он распознает все расположенные рядом с персонажем объекты. С полученным списком можно поступать как угодно (например, вы можете установить бомбу и применить к объектам силу взрыва), сейчас же мы хотим попытаться вызвать для всех этих объектов метод Operate().
192 Глава 8. Добавление в игру интерактивных устройств и элементов
Для вызова этого метода вместо обычной точечной нотации используется метод SendMessage(). Этот подход вы уже встречали раньше, при работе с кнопками UI. В данном случае мы прибегаем к нему из-за отсутствия точных данных о типе целевого объекта. А метод SendMessage() работает со всеми объектами GameObjects. Правда, на этот раз мы снабдим его параметром DontRequireReceiver. Дело в том, что у большинства объектов, возвращаемых методом OverlapSphere(), метод Operate() отсутствует. Обычно, если объект не может получить сообщение, метод SendMessage() выводит сообщение об ошибке. Но сейчас это не требуется, так как мы и так знаем, что большая часть объектов проигнорирует рассылаемые сообщения.
Готовый сценарий нужно присоединить к объекту player. После этого для открытия и закрытия двери достаточно встать рядом с ней и нажать клавишу.
Осталась одна маленькая деталь. Пока что не имеет значения, как именно стоит персонаж, главное, чтобы он располагался достаточно близко к двери. Но можно сделать так, чтобы дверь открывалась только в случае, когда персонаж стоит к ней лицом. Напомню, что в главе 7 мы определяли направление движения персонажа, вычисляя скалярное произведение. Эта математическая операция с парой векторов возвращает значения в диапазоне от –1 до 1, где 1 означает одно и то же направление векторов, а –1 указывает на диаметрально противоположные направления. Следующий листинг демонстрирует код, который нужно добавить в сценарий DeviceOperator.
Листинг 8.3. Код, позволяющий открывать дверь, только стоя к ней лицом
...
foreach (Collider hitCollider in hitColliders) {
Vector3 direction = hitCollider.transform.position - transform.position;
if (Vector3.Dot(transform.forward, direction) > .5f) { ¬ |
Сообщение отправляется только |
hitCollider.SendMessage("Operate", |
при корректной ориентации персонажа. |
SendMessageOptions.DontRequireReceiver); |
|
} |
|
} |
|
... |
|
Чтобы воспользоваться скалярным произведением, первым делом нужно определить направление, которое будет проверяться. Это направление от персонажа к объекту; нужный вектор создается вычитанием координат игрока из координат объекта. Затем вызывается метод Vector3.Dot(), которому передаются вычисленный вектор направления и вектор движения персонажа вперед. Если возвращенный методом результат близок к 1 (особенно при наличии в коде условия «больше чем 0.5»), значит, векторы имеют практически одно и то же направление.
После этих исправлений дверь перестанет открываться, когда персонаж стоит к ней спиной, даже если он находится рядом с ней. Этот подход применим к устройствам любого вида. Чтобы убедиться в его гибкости, давайте рассмотрим другой пример устройства.
8.1.3. Управление меняющим цвет монитором
Мы создали закрывающуюся и открывающуюся дверь, но аналогичная схема управления применима к устройствам любого вида. Построим еще одно устройство со
8.1. Создание дверей и других устройств 193
сходным управлением; на этот раз это будет меняющий цвет монитор, прикрепленный к стене.
Создайте новый куб и расположите его таким образом, чтобы одна из граней практически соприкасалась со стеной. Например, я ввел в поля Position значения 10.9, 1.5, –5. Теперь создайте сценарий ColorChangeDevice, добавьте в него код из следующего листинга и присоедините сценарий к монитору. Запустите игру, подойдите к монитору и нажмите ту же самую клавишу, которой открывали дверь; монитор начнет менять цвет, как показано на рис. 8.2.
Рис. 8.2. Монитор, меняющий цвет
Листинг 8.4. Сценарий устройства, которое может менять цвет
using UnityEngine;
using System.Collections;
public class ColorChangeDevice : MonoBehaviour {
public void Operate() { ¬ Объявление метода с таким же именем, как в сценарии для двери.
Color random = new Color(Random.Range(0f,1f), Эти числа представляют собой RGB-значения Random.Range(0f,1f), Random.Range(0f,1f)); ¬ в диапазоне от 0 до 1.
GetComponent<Renderer>().material.color = random; ¬ Цвет задается в назначенном объекту материале.
}
}
Первым делом мы объявляем функцию с тем же самым именем, что и в сценарии управления дверью. В сценариях управления устройствами всегда фигурирует функция с именем Operate, именно она вызывает срабатывание клавиши. В данном случае код этой функции присваивает материалу объекта случайный цвет (напоминаю, что цвет не является атрибутом самого объекта, объекту назначается материал, а у него уже есть цвет).
ПРИМЕЧАНИЕ Хотя в большинстве приложений для компьютерной графики цвет определяется компонентами Red, Blue и Green, параметры объекта Color в Unity лежат в диапазоне от 0 до 1, а не от 0 до 255, как это обычно бывает (в том числе и в интерфейсе выбора цвета в Unity).
Итак, вы познакомились с первым подходом к управлению устройствами в играх и даже создали пару таких демонстрационных устройств. Другим способом взаимодействия с элементами сцены является столкновение. Именно этим мы и займемся в следующем разделе.