- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала
5.4. Совпадения и подсчет очков 129
5.4. Совпадения и подсчет очков
Для создания полнофункциональной игры Memory нам не хватает проверки совпадений. Хотя у нас есть выложенные на стол карты, открывающиеся при щелчке на них, они никак не связаны друг с другом. Нам же нужно, чтобы каждая открытая пара проверялась на совпадение.
Эта абстрактная логическая схема — проверка совпадений и соответствующая реакция — требует, чтобы карты в ответ на щелчок уведомляли сценарий SceneController. Для этого в данный сценарий следует добавить код следующего листинга.
Листинг 5.8. Сценарий SceneController, следящий за открываемыми картами
...
private MemoryCard _firstRevealed; private MemoryCard _secondRevealed;
public bool canReveal { |
Функция чтения, которая возвращает значение false, |
get {return _secondRevealed == null;} ¬ |
|
} |
если вторая карта уже открыта. |
|
|
... |
|
public void CardRevealed(MemoryCard card) { |
|
// изначально пусто |
|
} |
|
... |
|
Метод CardRevealed() мы сейчас напишем; просто пока нам нужен вспомогательный пустой метод, на который можно будет ссылаться в сценарии MemoryCard.cs без появления ошибок компиляции. Обратите внимание, что мы снова применяем функцию чтения, на этот раз чтобы определить, открыта ли вторая карта; возможность открыть карту дается игроку только при отсутствии в сцене двух открытых карт.
Теперь нам снова нужно отредактировать сценарий MemoryCard.cs, добавив туда вызов метода (пока пустого), который будет информировать компонент SceneController о том, что игрок щелкнул на карте. Внесите в код сценария MemoryCard.cs изменения в соответствии со следующим листингом.
Листинг 5.9. Отредактированный сценарий MemoryCard.cs для открытия карт
...
public void OnMouseDown() { |
|
Проверка свойства canReveal контроллера, |
if (cardBack.activeSelf && controller.canReveal) { |
¬ позволяющая гарантировать, что одновременно |
|
cardBack.SetActive(false); |
могут быть открыты всего две карты. |
|
controller.CardRevealed(this); ¬ Уведомление контроллера при открытии этой карты. |
||
} |
|
|
} |
Открытый метод, позволяющий компоненту |
|
public void Unreveal() { ¬ |
SceneController снова скрыть карту (вернув на место |
|
спрайт card_back). |
|
cardBack.SetActive(true);
}
...
Если поместить в метод CardRevealed() инструкцию отладки для проверки взаимодействия объектов, можно увидеть, что после щелчка на любой карте появляется тестовое сообщение. Но мы сначала обработаем одну открытую карту.
130 Глава 5. Игра Memory на основе новой 2D-функциональности
5.4.1. Сохранение и сравнение открытых карт
Объект-карта был передан в метод CardRevealed(), поэтому мы начнем следить за открываемыми картами. Введите код следующего листинга.
Листинг 5.10. Слежение за открываемыми картами в сценарии SceneController
...
public void CardRevealed(MemoryCard card) {
if (_firstRevealed |
== null) { ¬ Сохранение карт в одной из двух переменных |
|
_firstRevealed = |
card; |
в зависимости от того, какая из них свободна. |
}else {
_secondRevealed = card;
Debug.Log("Match? " + (_firstRevealed.id == _secondRevealed.id)); ¬
}
}
...
Сравнение
идентификаторов двух открытых карт.
Этот код сохраняет открытые карты в одной из двух переменных. Если первая переменная свободна, заполняется именно она; если ей уже присвоен другой объект-кар- та, заполняется вторая переменная, а затем проверяется, совпадают ли идентификаторы карт. В зависимости от результата инструкция отладки выводит на консоль значение true или false.
Пока наш код никак не реагирует на совпадения, он только проверяет их. Поэтому давайте запрограммируем ответ.
5.4.2. Скрытие несовпадающих карт
Мы снова воспользуемся сопрограммой, так как реакция на отсутствие совпадения должна включать в себя остановку выполнения, дающую игроку возможность рассмотреть открытые карты. Сопрограммы детально рассматривались в главе 3; коротко говоря, именно сопрограмма позволит нам сделать паузу в процессе проверки совпадения. Следующий листинг содержит код, который следует добавить в сценарий
SceneController.
Листинг 5.11. Сценарий SceneController, подсчитывающий очки или скрывающий карты при отсутствии совпадения
...
private int _score = 0; ¬ Еще одна переменная, добавленная в список в верхней части сценария SceneController.
...
public void CardRevealed(MemoryCard card) { if (_firstRevealed == null) {
_firstRevealed = card; } else {
_secondRevealed = card;
StartCoroutine(CheckMatch()); ¬ |
Единственная отредактированная строка в этой функции |
} |
вызывает сопрограмму после открытия двух карт. |
|
|
} |
|
private IEnumerator CheckMatch() {
if (_firstRevealed.id == _secondRevealed.id) {
_score++; ¬ Увеличиваем счет на единицу, если идентификаторы открытых карт совпадают.
5.4. Совпадения и подсчет очков 131
Debug.Log("Score: " + _score);
}
else {
yield return new WaitForSeconds(.5f);
_firstRevealed.Unreveal(); ¬ Закрытие несовпадающих карт. _secondRevealed.Unreveal();
}
_firstRevealed = null; ¬ Очистка переменных вне зависимости от того, было ли совпадение.
_secondRevealed = null;
}
...
Первым делом мы добавляем переменную _score, предназначенную для слежения; затем после открытия второй карты загружаем в метод CheckMatch() сопрограмму.
Вней есть два варианта кода — выбор варианта зависит от того, произошло ли совпадение. В случае совпадения сопрограмма не останавливает выполнение кода; команда yield просто пропускается. В противном же случае происходит остановка на полсекунды, а потом для обеих карт снова вызывается скрывающий их метод Unreveal().
Вконце, вне зависимости от совпадения, переменные, в которых хранятся карты, обнуляются, давая игроку возможность открыть следующие карты.
Впроцессе игры несовпадающие карты будут некоторое время демонстрироваться игроку. Подсчет совпадений выводится в виде отладочных сообщений, нужно же сделать так, чтобы они выводились на экран.
5.4.3. Текстовое отображение счета
Отображение сведений для игрока является одной из причин добавления в игру пользовательского интерфейса (вторая причина состоит в необходимости предоставить игроку способ ввода информации; UI-кнопки мы обсудим в следующем разделе).
ОПРЕДЕЛЕНИЕ Употребляя аббревиатуру UI (User Interface — пользовательский интерфейс), обычно имеют в виду графический интерфейс пользователя (Graphical User Interface, GUI), который означает визуальную часть интерфейса, то есть текст и кнопки.
В Unity существуют разные способы отображения текста. Можно, к примеру, создать
всцене трехмерный текстовый объект. Это специальный сеточный компонент, поэтому нам нужен пустой объект, к которому его можно будет присоединить. Выберите
вменю GameObject команду Create Empty. Затем щелкните на кнопке Add Component и выберите в разделе Mesh вариант Text Mesh.
ПРИМЕЧАНИЕ Может показаться, что трехмерный текст несовместим с двухмерной игрой, но не следует забывать, что с технической точки зрения мы работаем с трехмерной сценой, которая выглядит плоской, так как демонстрируется через ортографическую камеру. Это означает, что при желании мы можем добавлять в игру трехмерные объекты — просто они будут отображаться как плоские.
Поместите этот объект в точку с координатами –4.75, 3.65, –10; то есть сдвиньте его на 475 пикселов влево и на 365 пикселов вверх, расположив в верхнем левом углу стола
132 Глава 5. Игра Memory на основе новой 2D-функциональности
и приблизив к камере, чтобы он отображался поверх других игровых объектов. Найдите в нижней части панели Inspector параметр Font; щелкните на маленьком кружке, чтобы вызвать окно выбора файлов, и щелчком выделите единственный доступный шрифт Arial. В поле Text введите слово Score:. Корректное позиционирование требует, чтобы параметр Anchor имел значение Upper Left (он контролирует, каким образом растягиваются вводимые буквы), поэтому отредактируйте его, если требуется. По умолчанию вы получите размытый текст, но это легко исправить, введя параметры, представленные на рис. 5.10.
С а а а
а а
Д |
С а а€ |
а а : |
‚ а |
Text, Anchor Font |
|
Рис. 5.10. Параметры текстового объекта на панели Inspector, позволяющие получить четкий и контрастный текст
Если импортировать в проект новый шрифт формата TrueType, можно будет им воспользоваться, но для наших целей вполне подойдет вариант, предлагаемый по умолчанию. Достаточно странно, что для получения четкой и контрастной надписи шрифтом, предлагаемым по умолчанию, требуется изменить его размер. Сначала мы присвоили параметру Font Size компонента Text Mesh очень большое значение (у меня это значение 80). Затем сделали масштаб этого компонента очень маленькими (например, 0.1, 0.1, 0.1). Увеличение размера шрифта добавило к отображаемому тексту множество пикселов, а масштабирование сгруппировало эти пикселы на меньшем пространстве. Для дальнейшего управления объектом нужно внести в код изменения, показанные в следующем листинге.
Листинг 5.12. Вывод счета при помощи текстового объекта
...
[SerializeField] private TextMesh scoreLabel;
...
private IEnumerator CheckMatch() {