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

6.4. Обновление игры в ответ на события 155
ВНИМАНИЕ Функция OnSubmitName() присутствует как в верхнем списке Dynamic String, так и в нижнем Static Parameters. Но выбор варианта в нижнем списке даст возможность отправлять всего одну заранее заданную строку. А так как нам нужно, чтобы пересылалось любое введенное в поле значение, то есть динамическая строка, выберите функцию OnSubmitName() в списке Dynamic String.
Повторите эту последовательность действий для ползунка: найдите поле событий в нижней части панели Inspector (в данном случае поле называется OnValueChanged), щелкните на кнопке со значком + (плюс) для добавления элемента, перетащите на ячейку объекта всплывающее окно и выберите в списке динамических функций вариант OnSpeedValue().
Теперь оба элемента управления вводом связаны с кодом сценария для всплывающего окна. Запустите игру и понаблюдайте за консолью в процессе перемещения ползунка или нажатия клавиши Enter после ввода имени в текстовое поле.
СОХРАНЕНИЕ ПАРАМЕТРОВ МЕЖДУ ИГРАМИ
В Unity есть несколько методов сохранения данных на постоянной основе, простейшим из которых является PlayerPrefs. Вам предоставляется абстрагированный вариант, работающий на всех платформах и с разными файловыми системами (то есть вам не нужно беспокоиться одеталях реализации), позволяющий сохранять небольшие фрагменты информации. Вслучае с большими объемами данных метод PlayerPrefs вам не очень поможет (в главе11 для сохранения данных впроцессе игры мы воспользуемся другими методами), но для сохранения настроек он подходит просто идеально.
Метод PlayerPrefs дает вам простые команды чтения и задания именованных значений (его работа во многом напоминает хэш-таблицу или словарь). Например, для сохранения выбранной скорости достаточно добавить в метод OnSpeedValue() сценария SettingsPopup строку PlayerPrefs. SetFloat(«speed», speed);. Этот метод сохранит десятичное значение скорости впеременной speed.
Аналогичным образом происходит инициализация ползунка с использованием сохраненного значения. Добавьте в сценарий SettingsPopup следующий код:
using UnityEngine.UI;
...
[SerializeField] private Slider speedSlider; void Start() {
speedSlider.value = PlayerPrefs.GetFloat("speed", 1);
}
...
Обратите внимание, что команде get предоставляется как значение переменной speed, так и значение по умолчанию на случай, если сохраненная скорость отсутствует.
Наши элементы управления генерируют отладочные сообщения, но пока никак не влияют на происходящее в игре. Программированию взаимодействий проекционного дисплея с игрой посвящен последний раздел нашей главы.
6.4. Обновление игры в ответ на события
До текущего момента проекционный дисплей и игра существовали отдельно друг от друга, в то время как они должны взаимодействовать. Мы уже программировали взаимодействия между объектами с помощью ссылок в сценариях, но в данном случае этот вариант нам не подходит, так как он приводит к формированию сильной связи между сценой и проекционным дисплеем. Нам же нужно, чтобы они были

156 Глава 6. Двухмерный GUI для трехмерной игры
практически независимы друг от друга, обеспечивая возможность свободно редактировать игру, не беспокоясь о сохранности проекционного дисплея.
Для информирования UI о происходящем в сцене мы создадим систему широковещательной рассылки сообщений. Принцип ее работы иллюстрирует рис. 6.17. Сценарии могут подписаться на слушание события, сообщение о котором рассылает конкретный код. Давайте посмотрим на практике, как все это работает.
|
|
|
С • а • • |
|
|
|
||
О а |
|
а , |
Д |
|||||
а а |
а • • |
• • • а а |
||||||
, а а а |
• а а |
|
• •. |
|||||
|
а а а |
|
Messenger |
|
С • а а |
|||
|
|
|
|
|
а |
|||
|
ListenObject |
|
|
• Д а а |
|
|
|
|
|
|
|
|
|
BroadcastObj |
|
||
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
• Awake() |
|
|
• Ра а • |
|
|
• Update() |
|
|
• OnEventReceived |
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 6.17. Схема широковещательной рассылки сообщений
СОВЕТ В языке C# существует встроенная система обработки событий. Скорее всего, у вас возник вопрос, почему мы ею не пользуемся. Дело в том, что эта система работает с нацеленными сообщениями, в то время как нам требуется широковещательная рассылка. В нацеленной системе код должен заранее знать источник сообщения, в то время как рассылка работает с сообщениями произвольного источника.
6.4.1. Интегрирование системы сообщений
Для оповещения UI о происходящем в сцене мы создадим систему широковещательной рассылки сообщений. Хотя в Unity нет подходящей для выполнения такой задачи встроенной функции, мы можем скачать нужный сценарий. Вики-сообщества Unify представляют собой репозиторий бесплатного кода от различных разработчиков. Их система для службы сообщений дает несвязанный способ взаимодействия с остальной частью программы посредством событий. Рассылающий сообщения код может ничего не знать о подписчиках, что позволяет легко менять или добавлять взаимодействующие объекты.
Создайте сценарий с именем Messenger и скопируйте в него одноименный код со страницы http://wiki.unity3d.com/index.php/CSharpMessenger_Extended.
Далее вам понадобится еще один сценарий, который называется GameEvent. Его код приведен в следующем листинге.
Листинг 6.7. Сценарий GameEvent, который будет использоваться вместе со сценарием Messenger
public static class GameEvent {
public const string ENEMY_HIT = "ENEMY_HIT";
public const string SPEED_CHANGED = "SPEED_CHANGED";
}

6.4. Обновление игры в ответ на события 157
Этот сценарий задает константу для пары сообщений о событиях, что позволяет систематизировать сообщения, одновременно избавляя вас от необходимости вводить строку сообщения в разных местах.
Система оповещений о событиях готова, давайте ею воспользуемся. Начнем мы с сообщений от сцены к проекционному дисплею, а затем запрограммируем взаимодействие в противоположном направлении.
6.4.2. Рассылка и слушание сообщений сцены
До текущего момента вместо набранных игроком очков отображались значения времени — мы использовали таймер для тестирования функциональности нашего текстового дисплея. Нам же нужно, чтобы там отображалось количество пораженных игроком врагов, поэтому давайте отредактируем код сценария UIController. Первым делом удалите метод Update(), так как именно там находился тестовый код. В момент своей смерти враг генерирует событие. Следующий листинг заставит сценарий UIController слушать это событие.
Листинг 6.8. Добавление подписчиков на событие в сценарий UIController
...
private int _score;
void Awake() { |
Объявляем, какой метод отвечает |
|
Messenger.AddListener(GameEvent.ENEMY_HIT, OnEnemyHit); ¬ |
||
} |
на событие ENEMY_HIT. |
|
|
|
|
void OnDestroy() { |
|
При разрушении объекта удаляйте |
Messenger.RemoveListener(GameEvent.ENEMY_HIT, OnEnemyHit); ¬ |
||
} |
|
подписчика, чтобы избежать ошибок. |
|
|
void Start() { _score = 0;
scoreLabel.text = _score.ToString(); ¬ Присвоение переменной score начального значения 0. settingsPopup.Close();
}
private void OnEnemyHit() {
_score += 1; ¬ Увеличение переменной score на 1 в ответ на данное событие. scoreLabel.text = _score.ToString();
}
...
Первым делом обратите внимание на методы Awake() и OnDestroy(). Как и методы Start() и Update(), все члены класса MonoBehaviour автоматически реагируют на активацию или удаление объекта. Подписчик добавляется в методе Awake() и удаляется в методе OnDestroy(). Будучи частью системы широковещательной рассылки сообщений, при получении данного сообщения он вызывает метод OnEnemyHit(), который увеличивает переменную score на 1 и выводит новое значение на текстовый дисплей.
Подписчик события задан в коде UI, поэтому каждое поражение врага должно сопровождаться рассылкой соответствующего сообщения. Реагирующий на смерть врага

158 Глава 6. Двухмерный GUI для трехмерной игры
код находится в сценарии RayShooter.cs, поэтому добавьте туда код отправки сообщения из следующего листинга.
Листинг 6.9. Рассылка сообщения о событии в сценарии RayShooter
...
if (target != null) { target.ReactToHit();
Messenger.Broadcast(GameEvent.ENEMY_HIT); ¬ К реакции на попадания добавлена рассылка сообщения.
}else {
...
Запустите игру и убедитесь, что теперь текстовый дисплей отображает количество пораженных врагов. При каждом попадании счетчик должен увеличиваться на единицу. Мы успешно справились с программированием сообщений от трехмерной игры к двухмерному интерфейсу, теперь нам требуется рассылка в обратном направлении.
6.4.3. Рассылка и слушание сообщений проекционного дисплея
В предыдущем разделе сообщение о событии посылалось сценой и принималось проекционным дисплеем. Сходным образом UI-элементы могут посылать сообщения, которые будут слушаться как игроками, так и врагами. В результате параметры, которые игрок укажет во всплывающем окне, начнут влиять на настройки игры.
Откройте сценарий WanderingAI.cs и добавьте туда код следующего листинга.
Листинг 6.10. Добавление подписчика события в сценарий WanderingAI
...
public const float baseSpeed = 3.0f; ¬ Базовая скорость, меняемая в соответствии с положением ползунка.
...
void Awake() {
Messenger<float>.AddListener(GameEvent.SPEED_CHANGED, OnSpeedChanged);
}
void OnDestroy() {
Messenger<float>.RemoveListener(GameEvent.SPEED_CHANGED, OnSpeedChanged);
}
...
private void OnSpeedChanged(float value) { ¬ Метод, объявленный в подписчике для события SPEED_CHANGED. speed = baseSpeed * value;
}
...
Методы Awake() и OnDestroy() в данном случае тоже добавляют и удаляют подписчика, но на этот раз нам предстоит иметь дело еще и со значением, задающим скорость перемещения врага.
СОВЕТ В предыдущем разделе мы использовали обобщенное событие, в то время как система рассылки позволяет передавать не только сообщения, но и значения. Для этого к подписчику достаточно добавить определение типа; обратите внимание на дополнение <float> в команде задания подписчика.
Внесем аналогичные изменения в сценарий FPSInput.cs, чтобы повлиять на скорость перемещения игрока. Содержимое следующего листинга отличается от листинга 6.10 только значением переменной baseSpeed у игрока.