Последние рекорды серверов
Pro Nub

Физика CountJump

Опубликовано Kpoluk 13 Мая 2023 в 12:01
Прежде всего, разберёмся с тем, как работает duck. В статье про физику bhop’а в начале функции PM_PlayerMove вы могли заметить вызов PM_Duck. Посмотрим, что в ней происходит:


Бит IN_DUCK выставляется и обнуляется аналогично IN_JUMP, разве что вместо команд +jump и –jump его состояние определяют +duck и –duck. Помимо него в PM_Duck фигурируют ещё несколько переменных, описывающих состояние игрока. Например, pmove->bInDuck равна true в процессе приседания. Как только игрок оказался в сидячем положении, то в переменной pmove->flags выставляется флаг FL_DUCKING, а переменная pmove->usehull становится равной 1, что означает изменение физических размеров модельки. Чтобы лучше понять связь этих параметров, рассмотрим несколько ситуаций.

Приседание и вставание


Стоя на ровной поверхности, нажмём кнопку приседания, тем самым послав команду +duck. В этот же фрейм в pmove->cmd.buttons выставится бит IN_DUCK, а переменная pmove->bInDuck станет равной true. Следующие 0.4 секунды (40 фреймов при 100 fps) уровень взгляда будет опускаться, а вертикальная координата центра игрока pmove->origin[2] оставаться неизменной. И только по истечении этого времени центр игрока сместится на 18 юнитов вниз, моделька перейдёт в сидячее положение, выставится флаг FL_DUCKING, а pmove->bInDuck станет равной false.

Оставаясь в сидячем положении, начнём двигаться вперёд. Статья про физику стрейфов подсказывает, что зажатая клавиша W даёт pmove->cmd.forwardmove = 250, так что внутри функции PM_Duck получаем pmove->cmd.forwardmove = 250 * 0.333 = 83.25 юнита/с (примерно в 3 раза меньше). Именно с такой скоростью мы будем передвигаться.

Когда мы отпустим кнопку приседания, послав команду –duck, вызовется функция PM_UnDuck:


Здесь движок с помощью трейса пытается переместить нас на 18 юнитов вверх, причём сначала в сидячем положении, а затем в стоячем. В случае успеха помимо нашего положения резко меняется уровень взгляда, обнуляются флаг FL_DUCKING и переменная pmove->usehull, а pmove->bInDuck становится равной false.

В воздухе же приседание происходит за один фрейм, в течение которого pmove->bInDuck переключается в true и обратно в false, уровень взгляда резко меняется с 17 юнитов над центром модельки до 12, при этом перемещения на 18 юнитов вниз нет. Переход в стоячее положение происходит также за один фрейм и без подъёма на 18 юнитов.


Уменьшение переменных pmove->cmd.forwardmove и pmove->cmd.sidemove в приседе приводит к уменьшению прибавки к вектору скорости (вспомните функции PM_AirMove и PM_AirAccelerate, которые мы обсуждали в статье про физику стрейфов), так что набирать скорость или даже просто менять её направление в воздухе становится значительно сложнее. При этом для получения наибольшего прироста мышь придётся вести плавнее, чем в стоячем положении.


Stand-Up Bhop


Теперь мы можем понять, как происходит stand-up bhop. В полёте мы зажимаем duck, за один фрейм переходя в сидячее положение. Как мы только что выяснили, в таком положении набирать скорость становится сложнее. Приближаясь к земле, мы отпускаем duck, вызывая тем самым PM_UnDuck. Она с помощью трейса проверяет, не застрянет ли где-нибудь моделька, если перевести её в стоячее положение. Пока мы находимся возле земли, застревание нам обеспечено, поэтому после трейса PM_UnDuck ничего не делает. Флаг FL_DUCKING не сбрасывается, скорость по-прежнему набирается с трудом.

Когда мы наконец оказываемся на земле, то трейс происходит до точки, которая на 18 юнитов выше положения игрока (то есть функция проверяет, не помешает ли нам что-либо, если мы встанем). Если бы мы были в туннеле, то встать не получилось бы, и мы сделали бы обычный bhop в приседе. Однако если сверху ничего не мешает, то мы тут же встанем. Независимо от того, прыгнем мы в этот же фрейм или позже, PM_Jump вызовется уже после PM_Duck, поэтому прыжок будет происходить из стоячего положения, как будто никакого приседания и не было. Вообще, отжимать кнопку duck’а можно было прямо на земле, главное чтобы это произошло не позже фрейма с командой прыжка. Но, конечно, безопаснее это делать немного заранее, чтобы случайно не опоздать.

Scroll Duck


Находясь на земле, попробуем сделать scroll duck (то есть duck, выполненный при помощи скролла). С точки зрения движка это будет выглядеть как команда +duck;-duck, которая приведёт к выставлению бита IN_DUCK в переменной pmove->cmd.buttons, и в PM_Duck начнётся процесс приседания, уровень взгляда чуть опустится. Если бы мы при этом бежали со скоростью v, то, согласно статье про физику стрейфов, вызов PM_Friction отнял бы 4% от v, а PM_Accelerate не дала бы никакой прибавки (так как addspeed = wishspeed – currentspeed = v * 0.333 – (v – v * 0.04) < 0). К примеру, при беге вперёд на W скорость уменьшилась бы с 250 до 240 юнитов/с.

В следующем фрейме бит IN_DUCK уже обнулён, поэтому вызывается функция PM_UnDuck, которая подбрасывает нас на 18 юнитов вверх. Получается, что вместо приседания мы сделали что-то вроде маленького прыжка. И самое замечательное в этим минипрыжке то, что он не влияет на переменную fuser2, о которой шла речь в статье про физику bhop’а. Благодаря этому CS даёт нам несколько интересных приёмов:

1) double duck

По ступенькам высотой 18 юнитов и ниже можно подниматься без дополнительных действий, в то время как более высокие ступеньки потребуют прыжка. Согласно оценке высоты прыжка с места при нулевом fuser2, приведённой в статье о физике bhop’а, мы можем запрыгнуть на ящики высотой до 45 юнитов (а последующее приседание увеличивает это значение до 45 + 18 = 63 юнитов). После запрыгивания на ящик fuser2 будет больше нуля, что замедлит наш последующий разгон.

Однако если вместо прыжка мы сделаем scroll duck, а затем сразу зажмём duck клавишей, то это позволит забираться на препятствия высотой до 35 юнитов, не затрагивая при этом fuser2! Другими словами, подъём по таким ступенькам можно совершать практически без потери скорости (если не считать 4%, отнимаемых PM_Friction). Именно этот приём имеет название double duck (или просто dd), хотя на практике scroll duck также называют dd.

2) приземление с dd

Если в полёте мы набрали достаточно большую скорость, то после приземления PM_Friction начнёт довольно быстро её урезать. Мы можем ослабить этот процесс, если, оказавшись на земле, сделаем dd. Это позволит перенести часть фреймов в воздух, где PM_Friction нам ничего сделать не может. При этом в зависимости от ситуации под dd здесь можно подразумевать и scroll duck в паре с обычным duck, и одиночный scroll duck, и серию идущих подряд scroll duck’ов (заметьте, что слишком резко прокручивать скролл в такой ситуации смысла нет, так как тогда «слипшиеся» команды дадут эффект обычного приседания).

3) CountJump и DoubleCountJump

При беге на ровной поверхности scroll duck поднимает нас в воздух на 18 юнитов, после чего мы падаем примерно 0.21 секунды (21 фрейм при 100 fps). За это небольшое время мы можем с помощью стрейфа не только восполнить потери от вызова PM_Friction, но и набрать дополнительную скорость. Если сразу после приземления сделать прыжок, то с одной стороны благодаря нулевому fuser2 он будет такой же долгий и высокий, как LongJump, а с другой стороны горизонтальная скорость во время отталкивания, как мы выяснили в статье про физику bhop’а, может достигать 299.973 юнитов/с. Такое сочетание даёт не только существенный прирост в дистанции, но позволяет эффективно бороться с edgefriction, описанным в статье про физику HighJump. По имени первооткрывателя Count техника получила название CountJump (или коротко cj).


На практике, даже при условии хорошего разгона перед scroll duck, достичь скорости под 300 юнитов/с за 21 фрейм крайне тяжело (чуть ниже мы сделаем оценку), поэтому вскоре появилась идея делать scroll duck с последующим набором скорости в воздухе дважды. Как вы знаете, такую технику назвали DoubleCountJump (или коротко dcj). Причём если стрейф после обоих scroll duck’ов делается в одну и ту же сторону, то получается, что разбег происходит в направлении, противоположном прыжку, отсюда и название: 180° DoubleCountJump.


Именно таким образом ставились первые dcj рекорды, однако на практике довольно сложно найти карту, где применение 180° dcj имело бы смысл. Более полезным оказалось умение делать Straight DoubleCountJump, когда на втором scroll duck’е происходит смена стрейфа, так что разбег и прыжок направлены в одну сторону.


Тут же стоит отметить, что аналогичную физику в момент прыжка (а значит, и потенциально возможную дистанцию) можно получить и с помощью других техник. Для этого достаточно, чтобы fuser2 был нулевым, то есть предыдущий прыжок был сделан раньше, чем 1.31 секунды назад. Например, под такое описание могут подойти DropBhop с достаточно долгим падением или WeirdJump.

4) GroundStrafe

Перед прыжком можно делать не один или два, а произвольное количество dd (здесь под dd будем подразумевать подскакивание с помощью scroll duck’а и последующее пребывание в воздухе). Однако поддерживать скорость при этом не так уж просто. Если сделать scroll duck не в первый фрейм на земле, а позже, то PM_Friction неизбежно отнимет у вас ещё больше скорости. Конечно, как и в случае bhop’а, вы можете прокрутить скролл, послав сразу несколько команд, но тогда нужно быть готовым к тому, что времени на возвращение используемого пальца в исходное положение останется меньше. Кроме того, активная прокрутка скролла может привести к тому, вы совершите полноценное приседание. Обсудим этот момент чуть подробнее.

Вообще говоря, если посмотреть на изменение вертикальной скорости во время double duck'а, то закрадывается подозрение, что мы должны провести в воздухе больше, чем 21 фрейм. Дело тут в том, что когда мы оказываемся на расстоянии меньше 2 юнитов до земли, то срабатывает специальная функция, телепортирующая нас на землю (подробнее о ней мы поговорим в статье про физику EdgeBug и JumpBug). Если в 21-ом фрейме сделать scroll duck, то моделька окажется в приседе, и телепорт в конце этого фрейма не сработает, зато сработает после вставания модельки в начале следующего фрейма (при этом PM_Friction в 22-ом фрейме всё равно будет вызвана, так что количество фреймов в воздухе можно считать неизменным). Однако если мы при активной прокрутке скролла сделали scroll duck ещё и в 22-ом фрейме, то вызванная в следующем 23-ем фрейме функция PM_UnDuck обнаружит, что вставание модельки привело бы к застреванию в земле, а значит, моделька должна остаться в сидячем положении, и мы продолжим падать на землю в приседе прямо как при stand-up bhop'е.

Прежде чем двигаться дальше, скажем пару слов о терминологии. Серию dd обычно называют dd run’ом. Когда-то было также предложено название GroundStrafe (сокращённо gs или gstrafe). Если же в конце dd run’а сделать прыжок, то получим MultiCountJump. Помимо этих названий можно встретить термин sgs, который расшифровывается как Stand-Up GroundStrafe. Зачастую понятия gs и sgs смешивают, однако, вообще говоря, это разные техники. При sgs делаются полноценные double duck’и, то есть чередуют scroll duck’и на земле и приседания в воздухе. Получается такой же эффект с падением на землю в приседе, о котором было написано выше, но здесь это происходит намеренно. Как и в случае stand-up bhop’а, приседание затрудняет набор скорости, однако позволяет дольше не касаться земли. Правда, у sgs есть одна важная особенность – после приземления нужен хотя бы один фрейм, чтобы встать, и только уже после него можно делать scroll duck. То есть касаемся земли мы реже, чем при gs, но времени на ней проводим больше (в терминах плагина статистики прыжков минимальное количество FOG, то есть фреймов на земле, для gs равно 2, а для sgs 3). Отсюда можно сделать вывод, что sgs может быть практичнее gs при больших горизонтальных скоростях либо в случае специфического рельефа карты.

В качестве примера возьмём карту smk_hnseu_airstrafes. Кореец LeblE в своей демке (скачать) использовал gs, в то время как румынский джампер brokoly показал прохождение с помощью sgs (скачать). Также нельзя не упомянуть интересный способ выполнения dd run'а от prize, который вместо отдельных прокручиваний сделал одно большое, проведя рукой по скроллу от кончика пальца практически до локтя (скачать).

Вообще, использовать dd run можно и вместо обычного бега или fastrun’а, но поддержание достаточно большой скорости (хотя бы большей 250-260 юнитов/с) потребует много тренировок. При этом сверху скорость не ограничена 300 юнитами/с, хотя сильно превысить это значение получится разве что при использовании настройки sv_airaccelerate 100, которая хоть и используется на некоторых серверах, но является нелегальной (как мы помним из физики стрейфов, по умолчанию квар sv_airaccelerate имеет значение 10). Продемонстрировать обычный dd run на стандартном de_nuke для нас согласился наш друг из Аргентины rawe (скачать).

Максимальный престрейф


Уже давно известно, что при DoubleCountJump можно набрать престрейф больше 300 юнитов/с, но что насчёт обычного CountJump? В статье про физику стрейфов мы узнали, что для поддержания оптимального угла u, максимизирующего скорость, нужно поворачивать мышь примерно на 5 градусов за фрейм. То есть для одного dd длительностью 21 фрейм получаем 105 градусов. При этом в статье про физику LongJump мы даже выяснили, что скорость в каждом фрейме будет меняться как Vnew = sqrt(V^2 + 875). Если взять максимальный престрейф перед dd за 277 юнитов/с, плюс учесть что PM_Friction заберёт свои 4%, оставив нам 265.92 юнита/с, то спустя 21 фрейм мы достигнем скорости 298.48 юнита/с - это и есть примерное значение максимального престрейфа для CountJump. Для сравнения FAME на 270 блоке имел в начале dd скорость 263.89 юнита/с, а в момент прыжка его престрейф достиг 293.25 юнита/с.

А что насчёт Stand-Up CountJump, когда в конце dd мы приседаем, получая дополнительные 9 фреймов в воздухе? При зажатом duck в PM_AirAccelerate попадёт wishspeed = 250 * 0.333, поэтому оптимальный угол u = arccos(8.325 / V), откуда, повторяя вычисления из статьи про физику LongJump, получаем формулу изменения скорости: Vnew = sqrt(V^2 + 830.69). Взяв 20 фреймов без duck и 10 фреймов с зажатым duck, находим, что престрейф в момент прыжка может быть больше 310 юнитов/с, что превышает 300 с большим запасом.

И ещё одна интересная оценка. Если бы нам каким-то образом удалось поддерживать оптимальный угол u при gstrafe (а ведь нам ещё желательно при этом менять клавишу стрейфа, чтобы не вертеться на месте, что делает задачу совсем нереальной), то мы смогли бы набрать скорость 464 юнита/с - начиная с неё вся набираемая за один dd скорость отнималась бы PM_Friction.

Работа duck в ограниченном пространстве


Поскольку высота модельки в стоячем положении составляет 72 юнита, то минимальная высота туннеля, в котором мы сможем передвигаться, равна 73 юнитам. В приседе рост в два раза меньше, поэтому в таком случае минимальная высота туннеля будет равняться 37 юнитам. Если мы присядем и попробуем прыгнуть в туннеле высотой 73 юнита, то обязательно заденем головой потолок, так как высота прыжка при нулевом fuser2 равна 45 юнитам (36 + 45 = 81 > 73). Что интересно, в том же туннеле мы можем прыгнуть и из стоячего положения, если заранее зажмём duck. Действительно, как мы выяснили ранее, после выставления бита IN_DUCK у нас есть 0.4 секунды, в течение которых центр модельки будет оставаться на одной высоте. Если в это время сделать прыжок, то после вызова PM_Jump мы окажемся в воздухе, а уже в следующем фрейме, не успев удариться головой, перейдём благодаря PM_Duck в сидячее положение. Правда лететь мы будем недолго, ведь до потолка останется всего 73 – 36 – 18 = 19 юнитов. Тем не менее, этот запас даёт нам возможность выпрыгивать из-под навесов, что оказывается очень полезным на kz картах.

А теперь зададимся вопросом – что будет, если стоя в туннеле сделать scroll duck? Туннель высотой 91 юнит и выше нам проблем не создаст, а вот при высоте от 73 до 90 юнитов мы попадём в интересную ситуацию: сначала PM_Duck начнёт приседание, выставив переменную pmove->bInDuck в true, а в следующем фрейме PM_UnDuck обнаружит, что поднять модельку на 18 юнитов вверх невозможно, и pmove->bInDuck так и останется true. В последующих вызовах PM_Duck будет думать, что мы в процессе приседания, и поэтому замедлит нас. При этом пока мы не нажмём кнопку duck’а и не присядем до конца, замедление не пропадёт. Ещё можно просто «выползти» из туннеля, тогда PM_UnDuck наконец подбросит нас на 18 юнитов вверх, выставив pmove->bInDuck в false.

В следующей части, посвящённой EdgeBug и JumpBug, duck также сыграет важную роль.