- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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.3. Управление инвентаризационными данными и состоянием игры 205
ВНИМАНИЕ Коллекции находятся в собственном пространстве имен, которое следует включить в сценарий; обратите внимание на дополнительную инструкцию using, появившуюся в верхней части листинга. Не забудьте добавить ее в свои сценарии!
Так как все диспетчеры реализуют интерфейс IGameManager, код может перечислить их как данный тип и вызвать определенный в каждом из них метод Startup(). Стартовая последовательность запускается как сопрограмма, то есть асинхронно с другими частями игры (например, анимированным индикатором выполнения на заставке).
Функция запуска сначала в цикле просматривает список диспетчеров и для каждого из них вызывает метод Startup(). Затем она входит в цикл, проверяющий, загрузился ли каждый из диспетчеров, и ожидает завершения этого процесса. После этого функция запуска уведомляет нас о загрузке и завершает свою работу.
СОВЕТ Инициализация написанных нами диспетчеров крайне проста и не связана с временем ожидания, но в общем случае эта стартовая последовательность на базе сопрограмм позволяет элегантно обрабатывать асинхронные задания запуска с долгим временем выполнения, такие как загрузка сохраненных данных.
Итак, наша структура кода готова. Вернитесь в редактор Unity и создайте пустой объект GameObject; как обычно, расположите его в точке с координатами 0,0,0 и присвойте ему значимое имя, например Game Managers. Свяжите с этим объектом сценарии
Managers, PlayerManager и InventoryManager.
В процессе воспроизведения игры вы не заметите никаких изменений, но на консоли появится ряд сообщений, информирующих о выполнении стартовой последовательности. Как видите, диспетчеры запускаются корректно, значит, пришло время заняться программированием диспетчера инвентаря.
8.3.3. Сохранение инвентаря в виде коллекции: списки и словари
Собранные персонажем элементы можно сохранить в виде объекта List. Следующий листинг добавляет такой список в диспетчер инвентаря.
Листинг 8.14. Добавление элементов в InventoryManager
...
private List<string> _items;
public void Startup() {
Debug.Log("Inventory manager starting...");
_items = new List<string>(); ¬ Инициализируем пустой список элементов.
status = ManagerStatus.Started;
}
private void DisplayItems() { ¬ Вывод на консоль сообщения о текущем инвентаре. string itemDisplay = "Items: ";
foreach (string item in _items) { itemDisplay += item + " ";
}
206 Глава 8. Добавление в игру интерактивных устройств и элементов
Debug.Log(itemDisplay);
}
public void AddItem(string name) { ¬ |
Другие сценарии не могут напрямую управлять списком |
_items.Add(name); |
элементов, но могут вызывать этот метод. |
|
|
DisplayItems(); |
|
} |
|
... |
|
В сценарии InventoryManager появилось два важных дополнения. Во-первых, мы добавили объект List, предназначенный для хранения элементов. Во-вторых, появился открытый метод AddItem(), который может вызываться другим кодом. Эта функция добавляет элемент в список и выводит список на консоль. Теперь давайте слегка скорректируем сценарий CollectibleItem, добавив в него вызов нового метода AddItem(), как показано в следующем листинге.
Листинг 8.15. Применение нового диспетчера InventoryManager в сценарии CollectibleItem
...
void OnTriggerEnter(Collider other) { Managers.Inventory.AddItem(name); Destroy(this.gameObject);
}
...
Теперь в процессе сбора элементов персонажем вы увидите, как растет список инвентаря в выводимом на консоль сообщении. При этом всплывет недостаток такой структуры данных, как список: все собранные элементы одного типа (например, второй элемент Health) появятся в списке, как показано на рис. 8.6, хотя разумнее было бы подсчитывать количество таких элементов. Разумеется, можно себе представить вариант игры, в котором каждый элемент нужно отслеживать отдельно, но в большинстве игр подсчитывается количество копий одного элемента. Устранить этот недостаток вполне реально средствами списка, но более естественно и эффективно прибегнуть к другой структуре данных, которая называется словарем.
Рис. 8.6. Консольное сообщение, в котором однотипные элементы перечисляются несколько раз
ОПРЕДЕЛЕНИЕ Структура данных Dictionary также взята из языка C#. Доступ к записям словаря осуществляется по идентификатору (или ключу), а не по их местоположению. Словарь напоминает хэш-таблицу, но более гибок, так как ключами могут выступать данные практически любого типа (например, «верните записи для этого объекта GameObject»).
Отредактируйте код сценария InventoryManager, заменив структуру данных List структурой Dictionary. Для этого вставьте вместо кода из листинга 8.14 код следующего листинга.
8.3. Управление инвентаризационными данными и состоянием игры 207
Листинг 8.16. Словарь элементов в InventoryManager
...
private Dictionary<string, int> _items; ¬ При объявлении словаря указывается два типа: тип ключа и тип значения.
public void Startup() {
Debug.Log("Inventory manager starting...");
_items = new Dictionary<string, int>();
status = ManagerStatus.Started;
}
private void DisplayItems() { string itemDisplay = "Items: ";
foreach (KeyValuePair<string, int> item in _items) { itemDisplay += item.Key + "(" + item.Value + ") ";
}
Debug.Log(itemDisplay);
}
public void AddItem(string name) {
if (_items.ContainsKey(name)) { ¬ Проверка существующих записей перед вводом новых данных.
_items[name] += 1;
}else { _items[name] = 1;
}
DisplayItems();
}
...
По большому счету этот код выглядит так же, как и раньше, но появилось несколько отличий. Если раньше вы не сталкивались с такими структурами данных, как Dictionary, обратите внимание, что при ее объявлении указывается два типа. Если структура List объявляется только с одним типом (типом сохраняемых в нее значений), то Dictionary объявляет как тип ключей (то есть идентификаторов, по которым будет вестись поиск), так и тип значений.
В методе AddItem() появился дополнительный алгоритм. Если раньше каждый элемент просто добавлялся в список, теперь первым делом требуется проверить наличие такого же элемента в словаре; именно этим занимается метод ContainsKey(). Если перед нами новый элемент, счет начинается с единицы, если же такой элемент уже существует, на единицу увеличивается сохраненное значение. Воспроизведите сцену, и вы убедитесь, что теперь в сообщениях о сборе инвентаря элементы одного типа объединяются, как показано на рис. 8.7.
Рис. 8.7. Консольное сообщение, в котором элементы одного типа объединяются
Наконец-то мы систематизировали в инвентаре игрока собранные элементы! Скорее всего, вам кажется, что для решения такой простой задачи мы написали слишком