- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала
78 Глава 3. Добавляем в игру врагов и снаряды
СТРУКТУРА КОДА ДЛЯ AI
Приведенный в этой главе код для AI помещен в один класс, так что изучить и понять его достаточно просто. Такая структура кода совершенно нормальна для простого искусственного интеллекта, поэтому не бойтесь, что вы сделали что-то не так и что на самом деле требуется более сложная структура. Для усложненных реализаций искусственного интеллекта (например, в игре с широким диапазоном интеллектуальных персонажей) облегчить разработку AI поможет более надежная структура кода.
Как упоминалось в главе 1 при сравнении компонентной структуры с наследованием, иногда фрагменты кода для AI имеет смысл помещать в отдельные сценарии. Это позволит сочетать и комбинировать компоненты, генерируя уникальное поведение для каждого персонажа. Подумайте, в чем ваши персонажи одинаковы, а чем различаются. Именно эти различия помогут вам при разработке архитектуры кода. К примеру, если в игре есть враги, сломя голову несущиеся на персонажа, и враги, тихо подкрадывающиеся в тени, имеет смысл создать для них разные компоненты перемещения и написать отдельные сценарии для перемещения бегом и перемещения подкрадываясь.
Точная структура кода для AI зависит от особенностей конкретной игры; единственного «правильного» варианта в данном случае не существует. Средства Unity позволят вам легко спроектировать гибкую архитектуру.
3.4. Увеличение количества врагов
В настоящее время в сцене присутствует всего один враг, и после его смерти сцена становится пустой. Давайте заставим игру порождать врагов, сделав так, чтобы после смерти существующего врага сразу появлялся новый. В Unity это легко делается с помощью механизма шаблонов экземпляров (prefabs).
3.4.1. Что такое шаблон экземпляров?
Шаблоны экземпляров предлагают гибкий подход к визуальному определению интерактивных объектов. В двух словах, это полностью сформированный игровой объект (с уже присоединенными и настроенными компонентами), существующий не внутри конкретной сцены, а в виде ресурса, который может быть скопирован
влюбую сцену. Копирование может осуществляться вручную, чтобы гарантировать идентичность объектов-врагов (или других объектов) в каждой сцене. Но куда важнее то, что эти шаблоны могут порождаться кодом; поместить копии объектов
всцену можно не только вручную в визуальном редакторе, но и с помощью команд сценария.
ОПРЕДЕЛЕНИЕ Ресурсом (asset) называется любой файл, отображаемый на вкладке Project; это могут быть двухмерные изображения, трехмерные модели, файлы с кодом, сцены и т. п. Термин «ресурс» вскользь упоминался в главе 1, но до текущего момента мы не акцентировали на нем внимания.
Термин экземпляр (instance) относится также к создаваемым на основе класса объектам кода. Попытайтесь не запутаться в терминологии; словосочетание «шаблон экземпляра» относится к игровому объекту, существующему вне сцены, в то время как экземпляром называется помещенная в сцену копия объекта.
3.4. Увеличение количества врагов 79
3.4.2. Создание шаблона врага
Конструирование шаблона начинается с создания объекта. Так как мы планируем получить шаблон из объекта-врага, этот шаг уже сделан. Теперь нужно перетащить строку с названием этого объекта с вкладки Hierarchy на вкладку Project, как показано на рис. 3.7. Объект автоматически сохранится в качестве шаблона. На панели Hierarchy его имя будет выделено синим цветом, означающим, что теперь он связан с шаблоном экземпляров. Редактирование этого шаблона (например, добавление компонентов) осуществляется посредством редактирования объекта сцены, после чего в меню GameObject выбирается команда Apply Changes To Prefab. Но сейчас данный объект в сцене уже не нужен (мы собираемся порождать новые шаблоны, а не пользоваться уже имеющимся экземпляром), поэтому его следует удалить.
Д а а а а
а Hierarchy а а Project
Рис. 3.7. Процесс получения шаблона экземпляров
ВНИМАНИЕ Интерфейс для работы с шаблонами экземпляров не слишком удобен, а соотношения между шаблонами и их экземплярами в сценах порой достаточно нестабильны. К примеру, зачастую требуется перетащить шаблон в сцену для последующего редактирования, а после завершения этого процесса удалить объект. В первой главе я упоминал об этом как о недостатке Unity и надеюсь, что в следующих версиях последовательность действий будет усовершенствована.
Теперь у нас есть шаблон для заполнения сцены врагами, поэтому давайте напишем код, создающий его экземпляры.
3.4.3. Экземпляры невидимого компонента SceneController
Хотя сам по себе шаблон в сцене отсутствует, нам нужен некий объект, к которому будет присоединяться код, порождающий врагов. Поэтому мы создадим пустой игровой объект и добавим к нему сценарий, при этом сам объект останется невидимым.
СОВЕТ Использование пустого объекта GameObject для присоединения к нему компонентовсценариев является распространенной практикой при разработке в Unity. Этот трюк применяется для решения абстрактных задач и нереализуем при работе с конкретными объектами сцены. Unityсценарии присоединяются только к видимым объектам, но не для каждой задачи это имеет смысл.
80 Глава 3. Добавляем в игру врагов и снаряды
Выберите в меню GameObject команду Create Empty и присвойте новому объекту имя Controller. Убедитесь, что он находится в точке с координатами 0, 0, 0 (с технической точки зрения местоположение этого объекта не имеет значения, так как его все равно не видно, но, поместив его в начало координат, вы облегчите себе жизнь, если в будущем решите использовать его в цепочке наследования). Создайте сценарий SceneController, показанный в следующем листинге.
Листинг 3.10. Сценарий SceneController, порождающий экземпляры врагов
using |
UnityEngine; |
|
using |
System.Collections; |
|
public class SceneController : MonoBehaviour { |
Сериализованная переменная для связи |
[SerializeField] private GameObject enemyPrefab; ¬ с объектом-шаблоном. private GameObject _enemy; ¬ Закрытая переменная для слежения за экземпляром врага в сцене.
void Update() { ¬ Порождаем нового врага, только если враги в сцене отсутствуют. if (_enemy == null) {
_enemy = Instantiate(enemyPrefab) as GameObject; ¬ Метод, копирующий объект-шаблон. _enemy.transform.position = new Vector3(0, 1, 0);
float angle = Random.Range(0, 360); _enemy.transform.Rotate(0, angle, 0);
}
}
}
Присоедините этот сценарий к объекту-контроллеру, и на панели Inspector появится поле для шаблона врага с именем Enemy Prefab. Оно работает аналогично общедоступным переменным, но есть и важное отличие.
ВНИМАНИЕ Для ссылки на объекты в редакторе Unity я рекомендую закрытые переменные с атрибутом SerializeField, так как нам нужно отобразить поле новой переменной на панели Inspector, но при этом не хотелось бы, чтобы ее значение могли менять другие сценарии. Как объяснялось в главе 2, открытые переменные отображаются на панели Inspector по умолчанию (другими словами, их сериализацией занимается Unity), поэтому в большинстве руководств и примеров для всех сериализованных значений фигурируют общедоступные переменные. Но эти переменные могут быть модифицированы другими сценариями (ведь они являются общедоступными); в большинстве же случаев редактирование значений должно разрешаться только через панель Inspector.
Перетащите шаблон врага с вкладки Project на пустое поле переменной; при наведении на него указателя мыши вы должны увидеть, как поле подсвечивается, демонстрируя допустимость присоединения объекта (рис. 3.8). После присоединения к шаблону врага сценария SceneController воспроизведите сцену, чтобы посмотреть, как работает код. Враг, как и раньше, будет возникать в центре комнаты, но если вы его застрелите, на его месте появится новый враг. Это намного лучше, чем единственный враг, который умирает навсегда!
СОВЕТ Перетаскивание объектов на поля переменных панели Inspector — весьма удобный прием, используемый в самых разных сценариях. В данном случае мы связали шаблон со сценарием, но можно связывать между собой объекты сцены и даже отдельные компоненты (так как код в этом конкретном компоненте должен вызывать открытые методы). В следующих главах мы еще не раз воспользуемся этим приемом.
3.4. Увеличение количества врагов 81
П а а а Project а
а Inspector
Рис. 3.8. Процедура соединения шаблона со сценарием
Центральной частью сценария является метод Instantiate(), поэтому обратите внимание на содержащую его строку. Созданные экземпляры шаблона появляются в сцене. По умолчанию метод Instantiate() возвращает новый объект обобщенного типа Object, который напрямую практически бесполезен, поэтому его следует обрабатывать как объект GameObject. В языке C# приведение типов осуществляется при помощи ключевого слова as (указывается исходный объект, ключевое слово as и желаемый новый тип).
Полученный экземпляр сохраняется в закрытой переменной _enemy типа GameObject (снова напоминаю о разнице между шаблоном экземпляра и самим экземпляром; переменная enemyPrefab хранит в себе шаблон, в то время как переменная _enemy — экземпляр этого шаблона). Инструкция if, проверяющая сохраненный объект, гарантирует, что метод Instantiate() будет вызван только при пустой переменной _enemy (или на языке кода — при равенстве этой переменной значению null). Изначально эта переменная пуста, соответственно, код создания экземпляра запускается в самом начале сеанса. Возвращенный методом Instantiate() объект затем сохраняется в переменной _enemy, блокируя повторный запуск кода создания экземпляров.
После попадания объект-враг разрушается, переменная _enemy становится пустой, что приводит к вызову метода Instantiate(). Благодаря этому враг всегда присутствует в сцене.
РАЗРУШЕНИЕ ИГРОВЫХ ОБЪЕКТОВ И УПРАВЛЕНИЕ ПАМЯТЬЮ
Тот факт, что существующие ссылки при разрушении объекта начинают указывать на значение null, является до некоторой степени неожиданным. В языках программирования с автоматическим управлением памятью, к которым относится C#, мы, как правило, не можем напрямую удалять объекты; можно обнулить все ведущие на них ссылки, после чего удаление объекта произойдет автоматически. Это верно и в Unity, но фоновый способ обработки объектов GameObject выглядит в Unity так, как если бы удаление совершалось напрямую.
Для отображения объектов в Unity ссылки на все эти объекты должны присутствовать в графе сцены. Соответственно, даже после удаления всех ссылок на конкретный игровой объект вкоде на него все равно будет ссылаться граф сцены, что сделает автоматическое удаление невозможным.