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

172      Глава 7. Игра от третьего лица: перемещение и анимация игрока

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

Величины перемещений присваиваются с помощью элементов управления вводом. Этот прием фигурировал и в предыдущем листинге, просто сейчас мы учитываем еще и скорость перемещения. Мы умножаем обе оси, вдоль которых происходит движение, на его скорость, а затем методом Vector3.ClampMagnitude() ограничиваем модуль вектора этой скоростью; иначе движение по диагонали будет иметь больший вектор, чем движение вдоль осей (нарисуйте катеты и гипотенузу прямоугольного треугольника).

Напоследок мы умножаем значения перемещения на параметр deltaTime, чтобы сделать данное преобразование независимым от частоты кадров (напоминаю, что это означает перемещение персонажа с одной и той же скоростью на разных компьютерах с разной частотой кадров). Полученные значения передаются методу Character­ Controller.Move(), который и приводит персонажа в движение.

Этот код обеспечивает нам перемещения в горизонтальном направлении, нам же нужно заставить персонаж перемещаться еще и по вертикали.

7.3. Выполнение прыжков

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

Position 1, 1.5, 5.5

Scale 4, 3, 4

Position 5, .75, 5

Scale 4, 1.5, 4

Рис. 7.8. Пара платформ, добавленных в сцену

Но сначала мы добавим в сцену пару возвышений. Ведь пока персонажу некуда запрыгивать и неоткуда падать! Создайте несколько кубов, поменяйте их размер по собственному вкусу и расположите в сцене. Лично я, как показано на рис. 7.8, добавил два куба со следующими параметрами:

7.3. Выполнение прыжков      173

Position: 5, 0.75, 5, Scale: 4, 1.5, 4;

Position: 1, 1.5, 5.5, Scale: 4, 3, 4.

7.3.1. Добавление вертикальной скорости и ускорения

Как я говорил, когда мы только начинали работать над сценарием RelativeMovement, значения перемещений вычисляются по отдельности и все время добавляются к вектору перемещения. Следующий листинг добавит к этому вектору движение по вертикали.

Листинг 7.4. Добавление движения по вертикали в сценарий RelativeMovement

...

public float jumpSpeed = 15.0f; public float gravity = -9.8f;

public float terminalVelocity = -10.0f; public float minFall = -1.5f;

private float _vertSpeed;

 

...

 

void Start() {

Инициализируем скорость по вертикали, присваивая ей минимальную

_vertSpeed = minFall; ¬

...

скорость падения в начале существующей функции.

 

}

 

void Update() {

 

...

Свойство isGrounded компонента CharacterController проверяет,

if (_charController.isGrounded) { ¬ соприкасается ли контроллер с поверхностью.

if (Input.GetButtonDown("Jump")) { ¬ Реакция на кнопку Jump при нахождении на поверхности.

_vertSpeed = jumpSpeed;

}else {

_vertSpeed = minFall;

}

}else { ¬ Если персонаж не стоит на поверхности, применяем гравитацию, пока не будет достигнута предельная скорость.

_vertSpeed += gravity * 5 * Time.deltaTime; if (_vertSpeed < terminalVelocity) {

_vertSpeed = terminalVelocity;

}

}

movement.y = _vertSpeed;

movement *= Time.deltaTime; ¬ Конец листинга 7.3, чтобы вы могли понять, куда вставлять новый код.

_charController.Move(movement);

}

}

Как обычно, мы начинаем с добавления в верхнюю часть сценария новых переменных для различных значений скорости и их корректной инициализации. Весь код до большой инструкции if, задающей перемещения по горизонтали, мы оставляем, а потом добавляем еще одну большую инструкцию if, задающую перемещения по вертикали. Этот новый фрагмент кода проверяет, стоит ли персонаж на поверхности, так как именно от этого зависит изменение вертикальной скорости. Это нам позволит установить значение свойства isGrounded компонента CharacterController; свойство

174      Глава 7. Игра от третьего лица: перемещение и анимация игрока

имеет значение true, когда нижняя часть контроллера персонажа сталкивается в последнем кадре с любым объектом.

Если персонаж стоит на поверхности, значение вертикальной скорости (это закрытая переменная _vertSpeed) становится нулевым. Раз персонаж не падает, очевидно, что его вертикальная скорость равна нулю; если после этого персонаж спрыгнет с края платформы, мы получим вполне натуральное падение, так как его скорость начнет расти от нуля.

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

Исключением является ситуация нажатия клавиши, отвечающей за прыжок. В этом случае вертикальная скорость должна увеличиться. Инструкция if проверяет результат метода GetButtonDown() — новой функции ввода, во многом аналогичной методу GetAxis(). Этот метод также возвращает состояние элемента управления вводом. Клавиша, отвечающая за прыжок, выбирается на панели Inspector, как и клавиши перемещения вдоль горизонтальной и вертикальной осей. Чтобы открыть нужный набор настроек, следует выбрать в меню Edit команду Project Settings Input (по умолчанию эта настройка имеет значение Space, то есть прыжок совершается путем нажатия клавиши пробела).

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

При этом код гарантирует, что скорость движения вниз не превысит предельного значения. Обратите внимание, что для этого используется оператор «меньше, чем», а не «больше чем», так как скорость движения вниз имеет отрицательное значение. После большой инструкции if рассчитанная вертикальная скорость присваивается оси Y вектора движения.

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

7.3.2. Распознавание поверхности с учетом краев и склонов

Как вы узнали в предыдущем разделе, свойство isGrounded компонента Character­ Controller показывает, сталкивалась ли в последнем кадре нижняя часть контроллера персонажа с каким-либо объектом. В большинстве случаев этот подход дает прекрасные результаты, но, возможно, вы уже заметили, что при попытке сойти с края

7.3. Выполнение прыжков      175

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

… а а а а

а а а а . П а В а а а а

а …

Рис. 7.9. Схема, демонстрирующая контакт капсулы контроллера и края платформы

Текущий вариант распознавания поверхности приводит к сходным проблемам и при попадании персонажа на наклонную плоскость. Создайте наклонный блок, опирающийся на одну из платформ. Например, у меня это был куб, для которого значения Position составили –1.5, 1.5, 5, Rotation — 0, 0, –25, а Scale — 1, 4, 4.

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

ПРИМЕЧАНИЕ  Персонаж должен скатываться вниз только с крутых склонов. Пологие склоны, например неровности почвы, следует пробегать, не обращая на них внимания. Если вы хотите получить такой вариант рельефа для тестирования, создайте куб и присвойте параметру Position значения 5.25, 0.25, 0.25, параметру Rotation — значения 0, 90, 75, параметру Scale — значения 1, 6, 3.

У всех этих проблем одна и та же причина: распознавание столкновений нижней частью персонажа не позволяет однозначно определить его положение по отношению к поверхности. Давайте вместо этого попробуем распознать поверхность методом бросания луча. В главе 3 именно так наш искусственный интеллект определял наличие перед ним препятствий; используем этот подход, чтобы выяснить тип поверхности под персонажем. Луч следует бросать вниз из точки, в которой он находится. Столкновение, зарегистрированное непосредственно под ногами персонажа, будет означать, что он стоит на поверхности.

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

176      Глава 7. Игра от третьего лица: перемещение и анимация игрока

с платформой, в то время как капсула касается ее края. Код должен каким-то образом обработать эту ситуацию.

 

… а а а а а

Л , а а

а а а а

а а, а а ,

а а

а

 

 

а …

 

 

 

 

 

 

 

 

Рис. 7.10. Схема бросания лучей вниз при сходе с края платформы

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

Следующий листинг добавляет в вертикальное движение все упомянутые нами аспекты.

Листинг 7.5. Распознавание поверхности методом бросания луча

...

private ControllerColliderHit _contact; ¬ Нужно для сохранения данных о столкновении между функциями.

...

bool hitGround = false; RaycastHit hit;

if (_vertSpeed < 0 && ¬ Проверяем, падает ли персонаж.

Physics.Raycast(transform.position, Vector3.down, out hit)) {

float check = ¬ Расстояние, с которым производится сравнение (слегка выходит за нижнюю часть капсулы).

(_charController.height + _charController.radius) / 1.9f; hitGround = hit.distance <= check;

}

if (hitGround) { ¬ Вместо проверки свойства isGrounded смотрим на результат бросания луча. if (Input.GetButtonDown("Jump")) {

_vertSpeed = jumpSpeed;

}else {

_vertSpeed = minFall;

}

}else {

_vertSpeed += gravity * 5 * Time.deltaTime; if (_vertSpeed < terminalVelocity) {

_vertSpeed = terminalVelocity;

}

Метод бросания луча не обнаруживает

if (_charController.isGrounded) { ¬ поверхности, но капсула с ней соприкасается.

7.3. Выполнение прыжков      177

 

if (Vector3.Dot(movement, _contact.normal) < 0) { ¬ Реакция слегка меняется в зависи-

movement = _contact.normal * moveSpeed;

мости от того, смотрит ли персонаж

} else {

в сторону точки контакта.

 

movement += _contact.normal * moveSpeed;

 

}

 

}

 

}

 

movement.y = _vertSpeed;

 

movement *= Time.deltaTime;

 

_charController.Move(movement);

 

}

 

 

При распознавании столкновения

void OnControllerColliderHit(ControllerColliderHit hit) { ¬

данные этого столкновения

_contact = hit;

сохраняются в методе обратного

вызова.

}

 

}

 

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

Следующие несколько строк касаются бросания луча. Этот код следует вставить после кода движения по горизонтали, но перед инструкцией if, которая задает вертикальное движение. С вызовом метода Physics.Raycast() вы уже сталкивались, но на этот раз мы используем другой набор параметров. Хотя луч бросается из той же самой точки (местоположения персонажа), он направляется не вперед, а вниз. Затем мы смотрим на расстояние, пройденное до момента столкновения; если оно совпадает с расстоянием до ступней персонажа, значит, персонаж стоит на поверхности и свойству hitGround можно присвоить значение true.

ВНИМАНИЕ  Процесс вычисления расстояния не вполне очевиден, поэтому рассмотрим его более подробно. Мы берем высоту контроллера персонажа (то есть его рост без скругленных углов) и добавляем к ней скругленные углы. Полученное значение делится пополам, так как луч бросается из центра персонажа и проходит расстояние до его стоп. Но мы хотим проверить чуть более длинную дистанцию, чтобы учесть небольшие неточности бросания луча, поэтому высота персонажа делится на 1.9, а не на 2, в итоге луч проходит несколько большее расстояние.

Далее в инструкции if для движения по вертикали вместо isGrounded используется свойство hitGround. Большая часть кода, управляющая перемещениями в вертикальном направлении, остается без изменений, мы просто добавляем туда код, обрабатывающий ситуацию, когда контроллер персонажа соприкасается с поверхностью, хотя персонаж на ней не стоит (то есть момент схода с края платформы). Здесь добавляется еще одна условная инструкция со свойством isGrounded, но обратите внимание, что она вложена в инструкцию проверки свойства hitGround. То есть свойство isGrounded проверяется только при условии, что свойство hitGround показало отсутствие поверхности.