Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Unity_в_действии_Джозеф_Хокинг_Рус.pdf
Скачиваний:
83
Добавлен:
21.06.2022
Размер:
26.33 Mб
Скачать

3.1. Стрельба путем бросания лучей      65

отправки сообщений и порождения объектов. Более того, поведение блуждающих целей — это еще один аспект, в моделировании которого нам пригодится прием испускания луча, так что мы познакомимся с еще одним вариантом его применения. Также широко применим демонстрируемый в рамках этого проекта подход к рассылке сообщений. В следующих главах вы встретите другие варианты применения указанных приемов. Впрочем, даже в рамках одной главы они используются в различных ситуациях.

По сути дела, мы добавляем в проект по одной функциональной возможности за раз, причем на каждом этапе в игру можно играть, но всегда остается ощущение, что каких-то элементов не хватает. Вот план нашей будущей работы:

1.Написать код, позволяющий игроку стрелять.

2.Создать статичные цели, реагирующие на попадание.

3.Заставить цели перемещаться по сцене.

4.Вызвать автоматическое появление новых блуждающих целей.

5.Дать возможность целям/врагам кидать в игрока огненные шары.

ПРИМЕЧАНИЕ  Проект, которым мы будем заниматься в этой главе, предполагает наличие демонстрационного ролика с перемещающимся по сцене персонажем. Этот ролик создавался в главе 2, но если вы ее пропустили, скачайте соответствующие файлы с сайта книги.

3.1. Стрельба путем бросания лучей

Первой дополнительной функциональной возможностью, которую мы добавим в наш демонстрационный ролик, станет стрельба. Умение оглядываться по сторонам и перемещаться в шутере от первого лица является, без сомнения, решающим, но игра не начнется, пока игроки не смогут влиять на окружающее пространство и показывать свое мастерство. Стрельба в трехмерных играх реализуется несколькими способами, наиболее важным из которых является бросание лучей.

3.1.1. Что такое бросание лучей?

Название приема подразумевает, что вам предстоит бросать лучи. Но что именно в данном случае подразумевается под словом луч?

ОПРЕДЕЛЕНИЕ  Лучом (ray) в сцене называется воображаемая, невидимая линия, начинающаяся в некоторой точке и распространяющаяся в определенном направлении.

Прием бросания луча иллюстрирует рис. 3.1. Вы формируете луч и затем определяете, с чем он пересекается. Подумайте, что происходит, когда вы стреляете из пистолета: пуля вылетает из точки, в которой находится пистолет, и летит по прямой вперед, пока не столкнется с каким-нибудь препятствием. Луч можно сравнить с путем пули, а бросание луча аналогично выстрелу из пистолета и наблюдению за тем, куда попадет пуля.

Думаю, очевидно, что реализация метода бросания лучей зачастую требует сложных математических расчетов. Трудно не только просчитать пересечение линии

66      Глава 3. Добавляем в игру врагов и снаряды

Л ,

Т а ,

И а а

( а )

Рис. 3.1. Луч представляет собой воображаемую линию, и при бросании луча выясняется,

счем этот луч пересекается

стрехмерной плоскостью, но и сделать это для всех сеточных объектов в сцене (напоминаю, что сеточным объектом называется трехмерный видимый объект, сконструированный из множества соединенных друг с другом линий и фигур). К счастью, Unity берет на себя все сложные математические расчеты, оставляя вам заботу о задачах более высокого уровня, например о месте и причине появления лучей.

В текущем проекте ответом на второй вопрос (почему возникает луч) является имитация выпускания пули. В шутерах от первого лица луч, как правило, начинается из места, где располагается камера, и распространяется по центру ее поля зрения. Иными словами, вы проверяете наличие объекта непосредственно перед камерой — в Unity есть соответствующие команды. Рассмотрим их более подробно.

3.1.2. Имитация стрельбы командой ScreenPointToRay

Итак, мы реализуем стрельбу проецированием луча, начинающегося в месте нахождения камеры и распространяющегося по центральной линии ее поля зрения. Проецирование луча по центральной линии поля зрения камеры представляет собой частный случай действия, которое называется выбором с помощью мыши.

ОПРЕДЕЛЕНИЕ  Выбор с помощью мыши (mouse picking) означает действие по выбору в трехмерной сцене точки, непосредственно попадающей под указатель мыши.

Эта операция в Unity осуществляется методом ScreenPointToRay(). Происходящее иллюстрирует рис. 3.2. Метод создает луч, начинающийся с камеры, и проецирует его по линии, проходящей через указанные экранные координаты. Обычно при выборе с помощью мыши используются координаты указателя, но в шутерах от первого лица эту роль играет центр экрана. Появившийся луч передается методу Physics. Raycast(), который и выполняет его «бросание».

Напишем код, использующий только что рассмотренные нами методы. Создайте в редакторе Unity новый компонент C# script и присоедините его к камере (а не к объекту, представляющему игрока). Добавьте в него код следующего листинга.

В листинге следует обратить внимание на несколько моментов. Во-первых, компонент Camera вызывается в методе Start(), совсем как компонент CharacterController в предыдущей главе. Остальная часть кода помещена в метод Update(), так как положение

 

3.1. Стрельба путем бросания лучей      67

 

 

И а а а,

Э а ( а

а а а

• )

а

 

Л а

а а

Рис. 3.2. Метод ScreenPointToRay() проецирует луч от камеры через указанные экранные координаты

Листинг 3.1. Сценарий RayShooter, присоединяемый к камере

using UnityEngine;

using System.Collections;

public class RayShooter : MonoBehaviour { private Camera _camera;

void Start() {

_camera = GetComponent<Camera>(); ¬ Доступ к другим компонентам, присоединенным к этому же объекту.

}

void Update() {

if (Input.GetMouseButtonDown(0)) { ¬ Реакция на нажатие кнопки мыши.

Vector3 point = new Vector3( ¬ Середина экрана — это половина его ширины и высоты.

_camera.pixelWidth/2, _camera.pixelHeight/2, 0);

Ray ray = _camera.ScreenPointToRay(point); ¬ Создание в этой точке луча методом ScreenPointToRay().

RaycastHit hit;

Испущенный луч заполняет информацией

if (Physics.Raycast(ray, out hit)) {

¬ переменную, на которую имеется ссылка.

Debug.Log("Hit " + hit.point); ¬ Загружаем координаты точки, в которую попал луч.

}

}

}

}

мыши нам требуется проверять снова и снова. Метод Input.GetMouseButtonDown() в зависимости от того, нажимается ли кнопка мыши, возвращает значения true и false. Помещение этой команды в условную инструкцию означает, что указанный код выполняется только после щелчка кнопкой мыши. Мы хотим, чтобы выстрел возникал по щелчку мыши, именно поэтому условная инструкция проверяет ее состояние.

Вектор создается, чтобы определить для луча экранные координаты (напоминаю, что вектором называются несколько связанных друг с другом чисел, хранимых как единое целое). Параметры pixelWidth и pixelHeight дают нам размер экрана. Определить его центр можно, разделив эти значения пополам. Хотя координаты экрана являются двухмерными, то есть у нас есть только размеры по вертикали и горизонтали, а глубина отсутствует, вектор Vector3 все равно создается, так как метод ScreenPointToRay() требует данных этого типа (возможно, потому, что расчет луча

68      Глава 3. Добавляем в игру врагов и снаряды

включает в себя операции с трехмерными векторами). Вызванный для этого набора координат метод ScreenPointToRay() дает нам объект Ray (программный, а не игровой объект; эти два объекта часто путают).

Затем луч передается в метод Raycast(), причем это не единственный передаваемый в этот метод объект. Есть также структура данных RaycastHit; она представляет собой набор информации о пересечении луча, в том числе о точке, в которой возник луч, и об объекте, с которым он столкнулся. Используемый в данном случае синтаксис языка C# гарантирует, что структура данных, с которой работает команда, является тем же объектом, существующим вне команды, в противоположность ситуациям, когда в разных областях действия функции используются разные копии объекта.

В конце мы вызываем метод Physics.Raycast(), который проверяет место пересечения рассматриваемого луча, собирает информацию об этом пересечении и возвращает значение true в случае столкновения луча с препятствием. Так как возвращаемое значение принадлежит типу Boolean, метод можно поместить в инструкцию проверки условия, совсем как метод Input.GetMouseButtonDown() чуть раньше.

Пока что на пересечения код реагирует консольным сообщением с координатами точки, в которой луч столкнулся с препятствием (значения X, Y, Z мы обсуждали в главе 2). Но понять, в каком именно месте это произошло, или показать, где находится центр экрана (то есть место, через которое проходит луч), практически нереально. Поэтому давайте добавим в сцену визуальные индикаторы.

3.1.3. Добавление визуальных индикаторов для прицеливания и попаданий

Теперь нам нужно добавить в сцену два вида визуальных индикаторов: точку прицеливания в середине экрана и метку в месте столкновения луча с препятствием. Второе в случае шутера от первого лица лучше показывать как дырки от пуль, но мы пока будем обозначать это место сферой (и воспользуемся сопрограммой, через секунду убирающей сферу). Рисунок 3.3 демонстрирует то, что вы увидите в сцене.

С а а а

а а

Т а а

а а

Рис. 3.3. Многократные выстрелы после добавления индикаторов прицеливания и попаданий

ОПРЕДЕЛЕНИЕ  Сопрограммы (coroutines) в Unity выполняются параллельно программе в течение некоторого времени; этим они отличаются от большинства функций, заставляющих программу ждать окончания своей работы.

3.1. Стрельба путем бросания лучей      69

Начнем с добавления индикаторов в точки попадания. Готовый сценарий показан в листинге 3.2. Подвигайтесь по сцене, стреляя; индикаторы в виде сфер выглядят довольно забавно!

Листинг 3.2. Сценарий RayShooter после добавления индикаторных сфер

using UnityEngine;

using System.Collections;

public class RayShooter : MonoBehaviour { private Camera _camera;

void Start() {

_camera = GetComponent<Camera>();

}

void Update() { ¬ Эта функция по большей части содержит знакомый нам код бросания луча из листинга 3.1. if (Input.GetMouseButtonDown(0)) {

Vector3 point = new Vector3(

_camera.pixelWidth/2, _camera.pixelHeight/2, 0); Ray ray = _camera.ScreenPointToRay(point); RaycastHit hit;

if (Physics.Raycast(ray, out hit)) { StartCoroutine(SphereIndicator(hit.point)); ¬ Запуск сопрограммы в ответ на попадание.

}

}

}

private IEnumerator SphereIndicator(Vector3 pos) { ¬ Сопрограммы пользуются функциями IEnumerator. GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); sphere.transform.position = pos;

yield return new WaitForSeconds(1); ¬ Ключевое слово yield указывает сопрограмме, когда следует остановиться.

Destroy(sphere); ¬ Удаляем этот GameObject и очищаем память.

}

}

Из нового у нас появился метод SphereIndicator() и однострочная модификация существующего метода Update(). Новый метод создает сферу в указанной точке сцены и через секунду удаляет ее. Вызов метода SphereIndicator() внутри кода испускания луча гарантирует появление визуальных индикаторов точно в местах попадания. Данная функция определена с помощью интерфейса IEnumerator, связанного с концепцией сопрограмм.

С технической точки зрения сопрограммы не асинхронны (асинхронные операции не останавливают выполнение остальной части кода; представьте, к примеру, скачивание изображения в сценарии веб-сайта), но продуманное применение перечислений в Unity заставляет сопрограммы вести себя аналогично асинхронным функциям. Секретным компонентом сопрограммы является ключевое слово yield, временно прерывающее ее работу, возвращающее управление основной программе и в следующем кадре возобновляющее сопрограмму с прерванной точки. В результате создается впечатление, что сопрограммы работают в фоновом режиме.

70      Глава 3. Добавляем в игру врагов и снаряды

Как следует из ее имени, функция StartCoroutine() запускает сопрограмму. После этого она начинает работать до завершения выполнения, периодически делая паузы. Обратитевниманиенанебольшую,новажнуюдеталь.ПереданныйвStartCoroutine() метод имеет набор скобок, следующий за именем. Такой синтаксис означает, что вы не передаете имя функции, а вызываете ее. И эта функция работает, пока не встретится команда yield. После этого ее выполнение на время прервется.

Функция SphereIndicator() создает сферу в определенной точке, останавливается после инструкции yield и после возобновления сопрограммы удаляет сферу. Продолжительность паузы контролируется значением, которое возвращается в момент появления инструкции yield. В сопрограммах допустимо несколько типов возвращаемых значений, но проще всего в явном виде вернуть время ожидания. Возвращая WaitForSeconds(1), мы заставляем сопрограмму остановить работу на одну секунду. Создание сферы, секундная остановка и разрушение сферы — такая последовательность дает нам временный визуальный индикатор.

Код для таких индикаторов был дан в листинге 3.2. Но нам требуется также точка прицеливания в центре экрана. Она создается в следующем листинге.

Листинг 3.3. Визуальный индикатор для точки прицеливания

...

void Start() {

_camera = GetComponent<Camera>();

Cursor.lockState = CursorLockMode.Locked; │ Скрываем указатель мыши

Cursor.visible = false;

в центре экрана.

}

 

 

void OnGUI()

{

 

int size =

12;

 

float posX

= _camera.pixelWidth/2 - size/4;

float posY

= _camera.pixelHeight/2 - size/2;

GUI.Label(new Rect(posX, posY, size, size), "*"); ¬ Команда GUI.Label() отображает на экране символ.

}

...

Еще один новый метод, добавленный в класс RayShooter, называется OnGUI(). В Unity возможны как базовая, так и усовершенствованная системы пользовательского интерфейса (UI). Так как базовая система обладает множеством ограничений, в следующих главах мы построим более гибкий, усовершенствованный пользовательский интерфейс, но пока нам проще отображать точку в центре экрана средствами базового интерфейса. Любой сценарий класса MonoBehaviour автоматически реагирует как на методы Start() и Update(), так и на функцию OnGUI(). Эта функция запускается в каждом кадре после визуализации трехмерной сцены, прорисовывая поверх сцены дополнительные элементы (представьте себе бумажные этикетки, наклеенные на нарисованный пейзаж).

ОПРЕДЕЛЕНИЕ  Визуализацией (rendering) называется работа компьютера по прорисовыванию пикселов трехмерной сцены. Хотя сцена задается с помощью координат X, Y и Z, монитор отображает двухмерную сетку цветных пикселов. Соответственно, для показа трехмерной сцены компьютер должен рассчитать цвет всех пикселов двухмерной сетки; работа этого алгоритма и называется визуализацией.