- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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.3. Отображение различных карт 123
присутствующий в сцене объект card_back на переменную этого сценария на панели Inspector, в процессе игры карта начнет исчезать после щелчка на ней. Исчезновение рубашки откроет лицевую сторону карты; как видите, мы решили еще одну важную для построения игры Memory задачу! Но пока на стол выложена всего одна карта, давайте исправим этот недостаток.
5.3. Отображение различных карт
Мы написали программу, которая сначала отображает рубашку карты, а после щелчка на ней показывает лицевую сторону. Но это всего лишь одна карта, тогда как для игры требуется целый набор, по большей части с разными изображениями. Мы разложим карты, воспользовавшись как парой концепций из предыдущих глав, так и совершенно новыми пока для вас понятиями. В главе 3 вы научились, во-первых, применять невидимый компонент SceneController, во-вторых, создавать экземпляры объекта. На этот раз компонент SceneController позволит нам назначить различным картам разные изображения.
5.3.1. Программная загрузка изображений
В создаваемой нами игре есть четыре варианта изображений для карт. Все восемь лежащих на столе карт (по две для каждого символа) получаются клонированием одного и того же оригинала, поэтому изначально на всех картах будет один рисунок. Менять его нам придется при помощи сценария, загружая различные изображения программно.
Чтобы посмотреть на процесс программной загрузки изображений, напишем простой тестовый код (который позже будет заменен). Сначала добавьте код следующего листинга в сценарий MemoryCard.
Листинг 5.3. Тестовый код, демонстрирующий изменение картинки спрайта
...
[SerializeField] private Sprite image; ¬ Ссылка на загружаемый ресурс Sprite void Start() {
GetComponent<SpriteRenderer>().sprite = image; ¬ Сопоставляем этот спрайт компоненту SpriteRenderer.
}
...
После сохранения этого сценария на панели Inspector появилась новая переменная, так как она была указана как сериализованная. Перетащите какой-либо спрайт со вкладки Project (выберите одно из лицевых изображений, кроме того, которое уже есть в сцене) и перетащите на ячейку Image. Затем выполните воспроизведение сцены, и вы увидите на карте новое изображение.
Ключом к пониманию этого кода являются сведения о компоненте SpriteRenderer. Обратите внимание, что на рис. 5.7 у рубашки карты всего два компонента: стандартный компонент Transform, присутствующий у всех объектов сцены, и новый компонент SpriteRenderer. Именно он превращает объект в спрайт и определяет, какой ресурс будет на нем отображаться. Обратите внимание, что первое свойство этого компонента называется Sprite и связано с одним из спрайтов на вкладке Project; этим свойством можно управлять в коде, чем, собственно, и занимается наш сценарий.
124 Глава 5. Игра Memory на основе новой 2D-функциональности
Р Sprite, а а
а Sprite
Ц , а • • Sprite ( а • , • а)
Рис. 5.7. Компонент SpriteRenderer, присоединенный к объекту-спрайту в сцене
Как вы видели в предыдущих главах на примере компонента CharacterController и написанных нами сценариев, метод GetComponent() возвращает другие компоненты для того же самого объекта, поэтому мы воспользуемся им для ссылки на объект SpriteRenderer. Свойству Sprite объекта SpriteRenderer можно присвоить любой ресурс, поэтому данный код присваивает указанному свойству объявленную в верхней части переменную Sprite (которую мы в редакторе заполнили одним из спрайтов).
Как видите, это не слишком сложно! Но это всего одно изображение, а у нас их четыре. Поэтому удалите новый фрагмент кода из листинга 5.3 (он был нужен только для демонстрации), чтобы подготовиться к следующему разделу.
5.3.2. Выбор изображения в невидимом компоненте
SceneController
Вспомните, как в главе 3 мы создавали в сцене невидимый объект для управления процессом генерации объектов. Мы снова воспользуемся этим приемом, но на этот раз невидимый объект будет контролировать более абстрактные вещи, не связанные ни с одним из объектов сцены. Для начала создайте пустой объект GameObject (напоминаю, что нужно выбрать в меню GameObject команду Create Empty). Затем создайте на вкладке Project сценарий SceneController.cs и перетащите этот ресурс на контроллер GameObject. Перед тем как писать код нового сценария, добавьте содержимое следующего листинга в сценарий MemoryCard вместо того, что было в листинге 5.3.
Листинг 5.4. Новые открытые методы в сценарии MemoryCard.cs
...
[SerializeField] private SceneController controller;
private int _id; public int id {
get {return _id;} ¬ Добавление функции чтения (идиома, распространенная в таких языках, как C# и Java).
} |
Открытый метод, которым могут пользоваться |
|
|
public void SetCard(int id, Sprite image) { ¬ |
другие сценарии для передачи указанному |
объекту новых спрайтов. |
|
_id = id; |
|
}
GetComponent<SpriteRenderer>().sprite = image; ¬ Точно такая же строка SpriteRenderer, как в удаленном примере кода.
...
5.3. Отображение различных карт 125
Основным отличием от предыдущего листинга является задание изображения спрайта методом SetCard() вместо метода Start(). Так как это открытый метод, принимающий спрайт в качестве параметра, его можно вызывать из других сценариев и устанавливать изображение для данного объекта. Обратите внимание, что метод SetCard() также принимает в качестве параметра значение ID и код его сохраняет. Хотя в настоящий момент этот параметр нам не требуется, скоро мы напишем код, сравнивающий карты, и это сравнение будет производиться именно на основе значения ID.
ПРИМЕЧАНИЕ Возможно, раньше вы пользовались языком программирования, в котором отсутствовали такие понятия, как метод чтения (getter) и метод задания (setter). Коротко говоря, это функции, которые запускаются, когда вы пытаетесь получить доступ к связанному с ними свойству (например, получить значение card.id). Эти методы применяются для разных целей, но в нашем случае свойство id предназначено только для чтения, поэтому вы можете только прочитать его значение, но не задать его.
Наконец, обратите внимание, что в этом коде присутствует переменная для контроллера; в то время как SceneController начинает клонировать карты для заполнения сцены, картам требуется ссылка на этот контроллер для вызова его открытых методов. Как обычно, когда код ссылается на объекты сцены, перетащите объект-кон- троллер из редактора Unity на ячейку переменной на панели Inspector. Достаточно сделать это для одной карты, а все появившиеся позже копии получат эту ссылку автоматически.
Теперь, когда мы добавили новый код в сценарий MemoryCard, введите код следующего листинга в сценарий SceneController.
Листинг 5.5. Первый проход компонента SceneController в игре Memory
using UnityEngine;
using System.Collections;
public class SceneController : MonoBehaviour {
[SerializeField] private MemoryCard originalCard; ¬ Ссылка для карты в сцене. [SerializeField] private Sprite[] images; ¬ Массив для ссылок на ресурсы-спрайты.
void Start() { |
|
int id = Random.Range(0, images.Length); |
Вызов открытого метода, который |
originalCard.SetCard(id, images[id]); ¬ |
|
} |
мы добавили в сценарий MemoryCard. |
|
|
} |
|
Это короткий фрагмент, демонстрирующий принцип управления картами контроллером SceneController. Большая часть представленного кода уже должна быть вам знакома (например, перетаскивание объекта-карты в редакторе Unity на ячейку переменной на панели Inspector), но с массивом изображений вы раньше не сталкивались. Как показано на рис. 5.8, на панели Inspector можно задать набор элементов. Введите 4 в поле размера массива, а затем перетащите спрайты с изображениями карт на ячейки элементов массива. Эти спрайты станут доступны в массиве, как и все остальные ссылки на объекты.
126 Глава 5. Игра Memory на основе новой 2D-функциональности
З а а
а а
Р-а
а а
а а а
Рис. 5.8. Заполнение массива спрайтов
Вообще говоря, мы пользовались методом Random.Range() в главе 3, надеюсь, вы это помните. Точные граничные значения в данном случае не важны, но имейте в виду, что минимальное значение входит в диапазон и может быть возвращено, в то время как возвращаемое значение всегда меньше максимального.
Щелкните на кнопке Play для воспроизведения нового кода. Вы увидите, что при каждом запуске сцены изображение на открываемой карте меняется. Теперь нужно разложить по столу все остальные карты.
5.3.3. Создание экземпляров карт
У компонента SceneController уже есть ссылка на объект-карту, поэтому нам остается воспользоваться методом Instantiate() (см. следующий листинг) для получения набора карт, аналогично тому, как мы генерировали объекты в главе 3.
Листинг 5.6. Восьмикратное копирование карты и выкладывание карт на стол
using UnityEngine;
using System.Collections;
public class SceneController : MonoBehaviour {
public const int gridRows = 2; ¬ Значения, указывающие количество ячеек сетки и их расстояние друг от друга. public const int gridCols = 4;
public const float offsetX = 2f; public const float offsetY = 2.5f;
[SerializeField] |
private MemoryCard originalCard; |
||
[SerializeField] private Sprite[] images; |
|
||
void Start() { |
|
|
Положение первой карты; положение |
|
|
|
|
Vector3 startPos = originalCard.transform.position; ¬ остальных карт отсчитывается от этой точки. |
|||
for (int i = |
0; i < gridCols; i++) { |
Вложенные циклы, задающие как столбцы, |
|
for (int j |
= 0; j < gridRows; j++) { |
¬ так и строки нашей сетки. |
|
MemoryCard card; ¬ Ссылка на контейнер для исходной карты или ее копий. |
|||
if (i == |
0 && j == 0) { |
|
|
card = |
originalCard; |
|
}else {
card = Instantiate(originalCard) as MemoryCard;
}
int id = Random.Range(0, images.Length); card.SetCard(id, images[id]);
float posX = (offsetX * i) + startPos.x;