Для игрока, незнакомого с миром kreedz, движение в воде зачастую рассматривается с позиции "прыгнул в воду, посмотрел куда хочешь плыть, зажал кнопку и вперёд". Для более опытного игрока к этому добавляется навык удержания на поверхности воды за счёт зажатой кнопки прыжка, а также возможность с помощью стрейфов набирать при этом скорость. Бывают правда странные моменты, когда выпрыгивание из воды будто срабатывает не с первого раза, да ещё и во время разгона стрейфами на поверхности нас будто что-то тормозит. В этой статье мы попробуем разобраться и с базовым движением в воде, и с более тонкими моментами.
На самом деле за физику передвижения в воде отвечает сразу ряд функций в
PM_PlayerMove:
Здесь сразу можно выделить несколько основных моментов:
- основная функция движения в воде это PM_WaterMove
- степень погружения в воду определяется переменной
waterlevel
, которая задаётся в функции PM_CheckWater
- при зажатой клавише прыжка в воде неким образом работает PM_Jump
- PM_WaterJump отвечает за специальный прыжок в воде, работа которого регулируется счётчиком
waterjumptime
Начнём с
PM_CheckWater, которая вызывается и внутри
PM_CategorizePosition, и отдельно в процессе специального прыжка:
Здесь по умолчанию предполагаем, что находимся не в воде, поэтому
waterlevel
нулевой. Далее берём точку на 1 юнит выше, чем уровень ног (для стоячей модельки 35 юнитов под центром игрока origin, для сидячей 17 юнитов), и, если она находится в воде, ставим
waterlevel
в 1. Если при этом центр модельки также погружён в воду, меняем
waterlevel
на 2. Ну а если в воде оказалась даже точка над
origin
на уровне глаз (17 юнитов выше центра стоячей модельки либо 12 юнитов выше сидячей модельки), то
waterlevel
становится равен 3.
Теперь из
PM_PlayerMove становится понятно, что передвижение в воде начинается с момента, когда погружается в воду центр модельки, т.е. условно когда мы находимся в воде хотя бы по пояс. Это передвижение можно разбить на несколько этапов. Сначала вычисляется желаемое направление скорости
wishvel
и её величина
wishspeed
:
Из зажатых клавиш
WASD и направления взгляда напрямую получается направление движения, к нему также может добавиться движение вверх за счёт команды
+moveup, если конечно игрок сможет найти у себя соответствующую клавишу. Правда принципиально это ничего не изменит, так как скорость затем всё равно обрезается до максимальной скорости с выбранным оружием, а затем сразу же теряет ещё 20% от величины. Отсюда сразу получаем, что скорость передвижения с usp в воде составляет 250 * 0.8 = 200 юнитов/с. Здесь же можно заметить, что если клавиши движения не нажаты, то игрок будет тонуть со скоростью 60 * 0.8 = 48 юнитов/с. Причём скорость передвижения с помощью клавиш зависит и от зажатого
+duck (250 * 0.333 * 0.8 = 66.6 юнита/с), и от использования
+speed (250 * 0.52 * 0.8 = 104 юнита/с), а вот скорость, с которой игрок тонет, от этих условий не зависит и всегда равна 48.
Далее следует часть, напоминающая по логике движение на земле, когда нас с одной стороны замедляет, с другой стороны мы пытаемся разогнаться:
Такая логика и не даёт нам мгновенно набрать скорость в начале движения, и имитирует инерцию при остановке. Когда торможение и ускорение уравновешивают друг друга, то мы получаем равномерное движение. Рассмотрим для примера движение вперёд со скоростью 200 юнитов/с. Желаемая скорость направлена вперёд,
wishspeed
= 200,
speed
= 200,
newspeed
= 200 - 4 * 1 * 0.01 * 200 = 0.96 * 200. Далее вектор скорости масштабируется, падая на 4%. Теперь считаем прибавку от разгонной части:
addspeed
= 200 - 200 * 0.96 = 0.04 * 200,
accelspeed
= 5 * 1 * 0.01 * 200 = 0.05 * 200. Так как
accelspeed > addspeed
, то
accelspeed
обрезается до
addspeed
, который прибавляется к модулю скорости. Таким образом, скорость возрастает ровно на ту же величину, которую теряла при торможении, а значит движение действительно равномерное.
Ну и последняя часть позволяет подниматься под водой по ступенькам и наклонным поверхностям:
Логика здесь значительно проще, чем в аналогичной проверке
PM_WalkMove, но тоже имеет свои особенности. Например, высота ступенек, по которым возможен подъём, в воде оказывается на 1 юнит больше и составляет таким образом 19 юнитов. Однако чтобы трейс не задел ступеньку, нужно при зажатой
W посмореть в горизонт или выше, так как это влияет на конечную точку трейса
dest
. И на самом деле можно даже чуть опустить взгляд, если вспомнить, что от ног до земли у нас есть дополнительные 0.03125 юнита, а трейс их в расчёт не берёт.
Разберёмся теперь с тем, какую роль в воде выполняет функция
PM_Jump. Её вызов в
PM_PlayerMove происходит при условии, что зажата клавиша прыжка, и при этом игрок погружён в воду как минимум по пояс:
Если в это время уже происходит специальный прыжок, то
PM_Jump лишь уменьшает его таймер и выходит. В противном случае вертикальная скорость игрока устанавливается в 100 юнитов/с по направлению вверх (в болоте скорость всплытия будет 80 юнитов/с, в остальных жидкостях типа лавы 50 юнитов/с). Опять же, эта скорость здесь не зависит от команд
+duck или
+speed. Далее раз в секунду проигрывается один из четырёх звуков, характерных для движения в воде.
После
PM_Jump происходит вызов
PM_WaterMove, в которой, если не нажаты клавиши движения, от скорости отнимается 4%. Если происходит всплытие, то его итоговая скорость равна 96 юнитам/с. Если же мы разбежимся и спрыгнем в воду с какой-либо высоты, отпустив все клавиши кроме прыжка, то мы будем выпрыгивать из воды с вертикальной скоростью 96 юнитов/с сразу же, как только оказались в ней по пояс, и таким образом совершать движения, похожие на колебания поплавка c периодом в 26 фреймов. При этом каждое касание воды будет отнимать 4% и от горизонтальной скорости, следовательно на большой скорости торможение будет ощущаться сильнее. К примеру, при скорости 250 юнитов/с спустя 10 касаний (2.6 секунды) останется 166 юнитов/с, а при начальной скорости 350 после 10 касаний останется 232 юнита/с. Поэтому для поддержания скорости необходимо использовать стрейфы, которые вне воды работают по уже известным нам алгоритмам для движения в воздухе.
Остаётся разобраться лишь со специальным прыжком. Возможен он лишь при
waterlevel
равном 2, то есть когда мы погружены по пояс, но уровень глаз находится выше поверхности воды. Следующие проверки происходят уже внутри
PM_CheckWaterJump:
Если специальный прыжок уже происходит, то новый не начнётся. Также специальный прыжок нельзя начать при падении со скоростью, превышающей 180 юнитов/с. Далее, если скорость ненулевая, но при этом между направлением горизонтальной скорости
flatvelocity
и направлением взгляда
flatforward
больше 90°, то выпрыгивания из воды также не произодёт. Далее делаем два трейса для точечного hull:
Начало первого трейса на 8 юнитов выше центра модельки, а его конец сдвинут относительно начала на 24 юнита в сторону направления взгляда. Если трейс наткнулся на плоскость, близкую к вертикальной (допустимое отклонение не более 5.74°), то происходит следующий трейс, также на 24 юнита в направлении взгляда, но уже на высоте, равной росту модельки:
Если же этот трейс в свою очередь ничего не задел, то только тогда выставляется флаг
FL_WATERJUMP
, счётчик
waterjumptime
устанавливается на 2000 фреймов (2 секунды), а нас подкидывает вверх со скоростью 225 юнитов/с (которая сразу же после этого в
PM_WaterMove потеряется 4% и станет равной 216 юнитам/с). С этого момента вместо рассмотренных нами
PM_CheckWaterJump,
PM_Jump и
PM_WaterMove работает функция
PM_WaterJump:
Помимо уменьшения счётчика
waterjumptime
она задаёт направление горизонтальной скорости вдоль горизонтальной составляющей вектора
movedir
. В свою очередь
movedir
был получен в
PM_CheckWaterJump как вектор, противоположный нормали к плоскости, с которой произошло взаимодействие. Это значит, что если выпрыгивание из воды происходит рядом со стеной, имеющей небольшой наклон, то во время подкидывания вверх нас будет ещё и прижимать к стене.
Скорость 216 юнитов/с будет сохраняться до тех пор, пока мы не покинем воду, в этот момент waterlevel обнулится,
PM_WaterJump сбросит
waterjumptime
, после чего мы за 28 фреймов поднимемся на высоту 30.24 юнита. Таким образом мы сможем запрыгнуть на стену, возвышающуюся над водой на 30 юнитов. Добавление приседания при этом добавит нам только 5 юнитов к возможной высоте стены, так как при высоте стены 36 юнитов над водой в функции
PM_CheckWaterJump второй трейс будет утыкаться в стену, и специального прыжка не произойдёт.
Напоследок обратим внимание на то, что для специального прыжка нам не потребовалось использовать клавишу прыжка. То есть для выпрыгивания достаточно подплыть к стене и посмотреть в её сторону (если стена параллельна базисной оси, то трейс в 24 юнита при половине ширины модельки в 16 юнитов позволяет при выпрыгивании отворачиваться в сторону от стены на
arccos(16/24) = 48.19°
). Требуемая степень погружения в воду при этом зависит от высоты стены по отношению к воде. К примеру, если они расположены вровень, то для инициализации специального прыжка необходимо погрузить центр модельки хотя бы на 8 юнитов в воду. Незнание этого нюанса зачастую мешает с первого раза выпрыгунть из воды и вызывает замешательство.