- •Предисловие
- •Введение
- •Благодарности
- •О книге
- •Перспективы
- •Условные обозначения, требования и доступные для скачивания данные
- •Автор в Интернете
- •Об авторе
- •Глава 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. Назначение материала
9.2. Скачивание сводки погоды из Интернета 227
_network = service;
StartCoroutine(_network.GetWeatherXML(OnXMLDataLoaded)); ¬ Начинаем загрузку данных из Интернета.
status = ManagerStatus.Initializing; ¬ Меняем состояние со Started на Initializing.
}
public void OnXMLDataLoaded(string data) { ¬ Метод обратного вызова сразу после загрузки данных.
Debug.Log(data);
status = ManagerStatus.Started;
}
...
В код этого диспетчера были внесены три основных изменения: запуск сопрограммы для скачивания данных из Интернета, задание другого состояния загрузки и определение метода обратного вызова для получения ответа.
С началом сопрограммы все просто. Все сложные аспекты работы с сопрограммами реализованы в сценарии NetworkService, поэтому сейчас вам остается только вызвать метод StartCoroutine(). Потом вы меняете состояние загрузки, так как на самом деле инициализация диспетчера не завершена; сначала он должен получить данные из Интернета.
ВНИМАНИЕ Методы передачи данных по сети всегда нужно начинать с функции StartCoroutine(); обычный вызов в их случае неприменим. Об этом легко забыть, так как создание веб-объектов за пределами сопрограммы не приводит к ошибкам компиляции.
Вызов метода StartCoroutine() должен сопровождаться активацией. То есть нужно добавить скобки, а не просто указать имя функции. В нашем случае в качестве одного из параметров методу сопрограммы требуется функция обратного вызова, которую нам следует задать. Для обратного вызова мы воспользуемся функцией OnXMLDataLoaded(); обратите внимание, что ее параметр относится к типу string, что совпадает с объявлением Action<string> в сценарии NetworkService. Пока что от функции обратного вызова нам много не требуется — она просто проверяет корректность полученных данных и выводит их на консоль. После этого последняя строчка функции меняет состояние загрузки диспетчера, уведомляя о том, что теперь он полностью загружен.
Щелкните на кнопке Play для воспроизведения кода. При наличии хорошего ин- тернет-подключения на консоли быстро появятся данные. Это всего лишь длинная строка, но отформатированная особым образом, что дает нам возможность воспользоваться содержащейся в ней информацией.
9.2.2. Парсинг текста в формате XML
Существующие в виде длинных строк данные обычно состоят из отдельных битов информации. Эти биты информации извлекаются методом синтаксического разбора, или, как его еще называют, парсинга.
ОПРЕДЕЛЕНИЕ Парсингом (parsing) называется процесс анализа фрагментов кода и его разделения на отдельные фрагменты данных.
228 Глава 9. Подключение игры к Интернету
Для парсинга строки она должна быть отформатирована таким образом, чтобы у вас (точнее, у кода-анализатора) была возможность идентификации отдельных фрагментов. Существует пара стандартных форматов передачи данных через Интернет; наиболее распространенным из них является XML.
ОПРЕДЕЛЕНИЕ Язык XML (Extensible Markup Language — расширяемый язык разметки) задает правила структурированного кодирования документов аналогично HTML-страницам.
К счастью, Unity (точнее, Mono — встроенная в Unity среда разработки) предлагает функциональность для анализа XML-кода. Запрошенный нами прогноз погоды имеет формат XML, поэтому добавим в сценарий WeatherManager код, анализирующий ответ и извлекающий из него информацию об облачности. Код вы найдете в Интернете, он довольно длинный, но нас интересует только фрагмент, содержащий, к примеру, такие данные, как <clouds value="40" name="scattered clouds"/>.
Мы не только добавим код, анализирующий XML, но и воспользуемся системой сообщений, знакомой нам по главе 6. Дело в том, что нам нужно уведомить сцену о скачанных и проанализированных данных. Создайте сценарий с именем Messenger
и вставьте в него код со страницы http://wiki.unity3d.com/index.php/CSharpMessenger_
Extended.
После этого нужно будет создать сценарий с именем GameEvent (его код приведен в следующем листинге). Как объяснялось в главе 6, эта система сообщений дает нам замечательный несвязанный способ информировать остальную часть программы о событиях.
Листинг 9.7. Код сценария GameEvent
public static class GameEvent {
public const string WEATHER_UPDATED = "WEATHER_UPDATED";
}
Теперь,когдаунасестьсистемасообщений,скорректируйтесценарийWeatherManager, как показано в следующем листинге.
Листинг 9.8. Парсинг XML-кода в сценарии WeatherManager
...
using System;
using System.Xml; ¬ Обязательно добавьте необходимые инструкции using.
...
public float cloudValue {get; private set;} ¬
...
public void OnXMLDataLoaded(string data) { XmlDocument doc = new XmlDocument();
doc.LoadXml(data); ¬ Разбиваем XML-код на структуру с возможностью поиска.
XmlNode root = doc.DocumentElement;
XmlNode node = root.SelectSingleNode("clouds"); ¬ Извлекаем из данных один узел. string value = node.Attributes["value"].Value;
cloudValue = Convert.ToInt32(value) / 100f; ¬ Преобразуем значение в число типа float в диапазоне от 0 до 1.
Debug.Log("Value: " + cloudValue);
9.2. Скачивание сводки погоды из Интернета 229
Messenger.Broadcast(GameEvent.WEATHER_UPDATED); ¬ Рассылка сообщения для информирования остальных сценариев.
status = ManagerStatus.Started;
}
...
Как видите, самые важные изменения появились внутри метода OnXMLDataLoaded(). Раньше он всего лишь выводил данные на консоль, позволяя нам убедиться в корректности их передачи. Теперь же мы добавили в метод команды, анализирующие XML-код.
Мы начинаем с создания нового пустого XML-документа; он послужит контейнером для разбираемой XML-структуры. Следующая строка разбивает строку данных, превращая ее в структуру из XML-документа. После чего мы начинаем с корня XML-дерева, чтобы в последующем коде можно было выполнять поиск по этому дереву.
На данном этапе в XML-структуре уже можно искать узлы, извлекая в итоге отдельные биты информации. Нас интересует только узел <clouds>. Первым делом мы ищем его в XML-документе, затем извлекаем из него атрибут value. Этот атрибут задает облачность в виде целого числа в диапазоне от 0 до 100, нам же для последующей корректировки вида сцены нужно число типа float в диапазоне от 0 до 1. Преобразование в данном случае осуществляется простой математической операцией.
Наконец, после извлечения из полных данных информации об облачности мы рассылаем сообщение об обновлении погодных данных. Пока что никто этого сообщения не слышит, но издатель не обязан ничего знать о подписчиках (собственно, в этом и состоит смысл несвязанной системы рассылки сообщений). Позднее мы добавим в сцену подписчиков.
Замечательно! Мы написали код, анализирующий XML-данные! Но, перед тем как воспользоваться полученным значением для изменения вида сцены, я хотел бы рассмотреть еще один формат передачи данных.
9.2.3. Парсинг текста в формате JSON
Перед тем как перейти к следующему этапу проекта, давайте познакомимся с альтернативным форматом передачи данных. Этот формат распространен так же, как XML, и называется JSON.
ОПРЕДЕЛЕНИЕ JSON (JavaScript Object Notation) — это текстовый формат обмена данными на основе JavaScript, назначение которого аналогично XML. Собственно, формат JSON проектировался как облегченная альтернатива XML. Хотя JSON-синтаксис является производным от языка JavaScript, на конкретный язык этот формат не ориентирован и легко используется с самыми разными языками программирования.
В отличие от XML-парсера, в среде разработки Mono парсер формата JSON отсутствует. Впрочем, есть ряд доступных для скачивания парсеров, например MiniJSON (https://gist.github.com/darktable/1411710). Создайте сценарий с именем MiniJSON
и вставьте туда код парсера. Теперь вы можете пользоваться этой библиотекой для анализа данных в формате JSON. Данные в формате XML мы получали от сервиса
230 Глава 9. Подключение игры к Интернету
OpenWeatherMap, но иногда часть данных оттуда поступает в формате JSON. Для получения таких данных отредактируйте сценарий NetworkService в соответствии со следующим листингом.
Листинг 9.9. Заставляем сценарий NetworkService запрашивать JSON-, а не XML-данные
...
private const string jsonApi = ¬ URL-адрес на этот раз выглядит чуть иначе.
"http://api.openweathermap.org/data/2.5/weather?q=Chicago,us";
...
public IEnumerator GetWeatherJSON(Action<string> callback) { return CallAPI(jsonApi, callback);
}
...
Фактически, у нас тот же самый код для скачивания XML-данных, просто в нем фигурирует немного другой URL-адрес. Этот запрос возвращает те же самые данные, что и раньше, но в другом формате. И на этот раз мы уже будем искать фрагмент кода
"clouds":{"all":40}.
Большое количество дополнительного кода теперь не потребуется. Ведь мы выделили код запросов в аккуратные отдельные функции, поэтому ничего сложного в добавлении последующих HTTP-запросов нет. Видите, как здорово! Давайте отредактируем сценарий WeatherManager, заставив его запрашивать данные в формате JSON, а не XML. Необходимый для этого код показан в следующем листинге.
Листинг 9.10. Заставляем сценарий WeatherManager запрашивать JSON-данные
...
using MiniJSON; ¬ Обязательно добавьте инструкцию using.
...
public void Startup(NetworkService service) { Debug.Log("Weather manager starting...");
_network = service;
StartCoroutine(_network.GetWeatherJSON(OnJSONDataLoaded)); ¬ Измененный сетевой запрос.
status = ManagerStatus.Initializing;
}
...
public void OnJSONDataLoaded(string data) {
Dictionary<string, object> dict; ¬ Разбираем не созданный нами XML-контейнер, а содержимое словаря. dict = Json.Deserialize(data) as Dictionary<string,object>;
Dictionary<string, object> clouds = |
│ |
(Dictionary<string,object>)dict["clouds"]; |
│ |
cloudValue = (long)clouds["all"] / 100f; |
│ |
Debug.Log("Value: " + cloudValue); |
│ |
|
│ |
Messenger.Broadcast(GameEvent.WEATHER_UPDATED); │ status = ManagerStatus.Started;
}
...
Синтаксис изменился, но функциональность кода осталась прежней.