Погрузимся в тему компиляции карт чуть глубже. Для компиляции повсеместно используются
zhlt (Zoner's Half-Life Tools) и его более новая доработанная версия
vhlt (Vluzacn's Half-Life Tools). Эти компиляторы состоят из четырёх утилит, которые запускаются последовательно:
hlcsg.exe - по информации из
*.map
файла карты, в котором лаконично записана информация об entity, брашах и используемых текстурах, подготавливает секции, связанные со структурой карты (двоичные деревья, плоскости, грани, вершины, рёбра, связи между ними), получая на выходе предварительную версию
*.bsp
файла (в игре его пока запустить нельзя), а также ряд временных файлов для последующей работы остальных утилит (для
hlbsp файлы
*.p0/1/2/3
с информацией о плоскостях, файлы
*.b0/1/2/3
со вспомогательной информацией по брашам, файл
*.hsz
с размерами оболочек, для
hlrad *.wa_
файл c информацией о текстурах и др.).
hlbsp.exe - на основе полученных от
hlcsg файлов производит проверку утечек, доразбивает или производит слияние граней, удаляет лишние узлы деревьев и на выходе даёт
*.bsp
файл, который уже можно запустить в игре, а также временные файлы (
*.ext
для
hlrad и
*.prt
для
hlvis с информацией о порталах, которых в CS впрочем нет, в
*.lin
и
*.pts
записывается информация об утечках).
hlvis.exe - разбивает всю карту на зоны, которые позволяют исключать из рендера области, заведомо не попадающие в поле зрения игрока, тем самым улучшив производительность (критично для больших карт). Полученная информация заносится в секцию LUMP_VISIBILITY файла
*.bsp
.
hlrad.exe - просчитывает освещение, занося данные в секцию LUMP_LIGHTING файла
*.bsp
.
По ходу работы утилит информация о ходе выполнения операций записывается в
*.log
файл, а в случае возникновения ошибок создаётся
*.err
файл, в котором можно узнать, что именно пошло не так. При этом работу утилит можно настраивать с помощью многочисленных ключей запуска, которые могут значительно влиять на структуру брашей. В частности, ключ
-cliptype утилиты
hlcsg помимо прочего определяет то, каким образом будут сдвинуты плоскости clip-деревьев относительно плоскостей основного дерева, и сопровождается одним из следующих параметров:
smallest, normalized, simple, precise, legacy.
Simple и Legacy
Про сдвиги вертикальных и горизонтальных плоскостей мы уже говорили, но вот для наклонных поверхностей возможны несколько вариантов. Посмотрим на
код hlcsg компилятора vhlt, для простоты взяв в качеcтве оболочки hull стоячего игрока и подставив значения констант:
Начнём с варианта 1, работающего для типов simple и smallest, как самого интуитивно понятного. Здесь для наклонных плоскостей мы просто комбинируем горизонтальный и вертикальный сдвиги:
Заметьте, что если в таком варианте при переходе с горизонтальной поверхности на наклонную смотреть вниз, то можно увидеть, как расстояние до земли ощутимо меняется. Судя по всему, создателям компилятора этот эффект не понравился, и они добавили вариант 2, работающий для типов legacy и normalized. Для него сдвиг clip-плоскостей зависит от направления нормали, то есть фактически от наклона поверхности. Пример подъёма под 45° даёт в варианте 2 следующие сдвиги:
Оболочка стоячего игрока оказывается утоплена в видимом браше на
(36 - 36 * cos(45°)) + (16 - 16 * cos(45°)) = 15.23
юнита по горизонтали. Соответственно расстояние от центра нижней грани параллелепипеда игрока до наклонной плоскости составляет всего 0.77 юнита по горизонтали и вертикали. При этом для сидячего игрока параллелепипед будет утоплен на
(18 - 18 * cos(45°)) + (16 - 16 * cos(45°)) = 9.96
юнита по горизонтали, а расстояние от центра нижней грани до земли составит
6.04
юнита, то есть на наклонной поверхности разность между центром игрока в сидячем и стоячем положениях оказывается меньше 18 юнитов. Как бы то ни было, для типа legacy визуально переход на наклонную поверхность меняет расстояние до земли значительно меньше, а значит успех, казалось бы, достигнут, но тут начинают появляться нюансы.
Взглянем на тестовый проект
hull_test.jmf
и две скомпилированные из него карты
hull_simple.bsp
и
hull_legacy.bsp
(файлы проекта и карты можно скачать
здесь):
На первый взгляд карта содержит два ряда идентичных объектов, и на
hull_simple.bsp
, скомпилированной с типом simple, никакой разницы между ними вы действительно не заметите. А вот на
hull_legacy.bsp
, скомпилированной с типом legacy, объекты в переднем ряду, состоящие из одного браша, существенно отличаются от объектов позади, состоящих из пары брашей. Начнём с уже знакомой платформы с подъёмом под 45°:
Разделение на два браша приводит к тому, что платформа в заднем ряду получает дополнительную плоскость clip-нода:
Как результат в конце подъёма нас ждёт невидимая ступенька высотой 15.23 юнита. Из статьи про
физику EdgeBug и JumpBug мы уже знаем, что при прохождении ступеньки не выше 18 юнитов движок просто подбросит нас на нужную высоту. Именно этот скачок мы можем наблюдать и на нашей платформе, что особенно хорошо заметно при медленном подъёме.
Далее на той же
hull_legacy.bsp
опробуем клиновидные браши в стене слева. На виде сверху они выглядят следующим образом:
Плоскости, расположенные под 45° к стене, получили сдвиги по X и Y (одинаковые для стоячей и сидячей модельки), причём клин в задней части комнаты, состоящий из двух брашей, также имеет дополнительную плоскость, в которую мы упрёмся, попытавшись двигаться вплотную к клину. Ступенькой этот невидимый выступ не является, так как лежит в другой плоскости, поэтому движок нам с его прохождением никак не помогает.
Следующие на очереди блоки с наклонной поверхностью наверху:
Высота нижней части этих блоков равна 64 юнитам, поэтому на версии
hull_simple.bsp
ни на один из них запрыгнуть было нельзя, однако на
hull_legacy.bsp
блок в ближнем ряду благодаря сдвигу наклонной плоскости оказывается для сидячей модельки выше не на 18 юнитов, а всего лишь на уже знакомые нам 6.04 юнита:
В то же время состоящий из двух брашей блок в заднем ряду имеет дополнительную горизонтальную плоскость, которая делает его неприступным:
Правее стоят блоки со слайдом:
На ближний блок можно забраться без проблем, а вот блок из заднего ряда не даёт прослайдить вверх из-за наличия дополнительной вертикальной плоскости. Таким образом, упомянутые особенности типа legacy являются важным напоминанием мапперам о том, насколько сильно внутреннее разбиение блоков влияет на физику игры.
Тип legacy является самым рапространённым среди карт в CS 1.6, включая стандартные карты. Связано это скорее всего с тем, что он использовался компилятором zhlt по умолчанию. При этом vhlt по умолчанию использует тип simple, однако игроки уже так привыкли к физике legacy, что большинство мапперов вручную добавляют
-cliptype legacy
в ключи запуска
hlcsg.
Precise
Хотя примеров использования типа precise не так много (из известных карт можно назвать
hfr_anoyo и
hfr_unshape), пару слов про него сказать стоит. В приведённом ранее отрывке кода часть под
#ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX
появилась в vhlt, в то время как zhlt использовал для precise старый вариант, фактически совпадающий с legacy для наклонных поверхностей круче 45° и с simple во всех остальных случаях. В коде также можно найти комментарии, указывающие на недостатки такого подхода, в частности на проблемы с отступом в
eps
, который мы обсуждали в статье про
физику pixelwalk. В связи с этим vhlt для типа precise в случае наклонных плоскостей, являющихся слайдами (то есть с углом наклона больше
acrcos(0.7) = 45.573°
), делает стандартный сдвиг на 18 или 36 юнитов по вертикали, при этом по горизонтали не делая сдвига вообще; для всех остальных плоскостей сдвиг аналогичен типу simple.
Максимальная высота блока
Начнём с простого. Пусть перед нами расположен куб со стороной 63 юнита. Из статьи про
физику bhop мы знаем, что расчётная высота прыжка с места равна 45 юнитам. Прибавляем сюда 18 за счёт приседания, и получаем как раз 63 юнита. Однако в действительности моделька игрока перемещается дискретно, поэтому 45 юнитов мы на самом деле не достигаем. Если обозначить как
V0
начальную вертикальную скорость после прыжка и вспомнить, что каждый фрейм эта скорость уменьшается на 8 юнитов/с, то получится, что максимальной высоты
h
мы достигнем спустя
N = [V0 / 8]
фреймов (квадратные скобки здесь означают целую часть числа). Далее считаем:
h = V0 * 0.01 + (V0 - 8) * 0.01 + (V0 - 8 * 2) * 0.01 + ... + (V0 - N * 8) * 0.01
h = V0 * (N + 1) * 0.01 - 0.01 * 8 * (1 + 2 + ... + N)
h = 0.01 * (N + 1) * (V0 - 4 * N)
Из той же статьи про физику bhop вспоминаем, что начальная вертикальная скорость равна
sqrt(2 * 800 * 45) = 268.3281573
юнита/с, плюс не забываем, что на момент трейса
PM_FixupGravityVelocity успевает отнять 4 юнита/с, итого
V0 = 264.3281573
юнита/с. Отсюда
N = 33
и
h = 44.991573
юнита. Выходит, что с такой высотой прыжка мы до 63 юнитов не дотянем. Почему же у нас получается запрыгивать на такую высоту?
И вот тут к нам на помощью приходит отступ
eps = 0.03125
юнита из статьи про физику pixelwalk. Этот зазор между моделькой игрока и уровнем пола даёт недостающие сотые доли и позволяет превысить высоту куба в 63 юнита на 0.022823 юнита. Правда, в таком случае после запрыгивания на куб зазор между моделькой и верхом куба останется равным именно 0.022823, а не 0.03125. Это значит, что если мы попытаемся с этого куба запрыгнуть на ещё один такой же, то запаса по высоте уже будет меньше, и новый зазор тоже станет меньше. После третьего такого куба подряд зазор практически исчезнет, а на четвёртый куб высоты прыжка уже не хватит. Чтобы сбросить зазор до
eps
, достаточно сделать после третьего куба doubleduck или обычный прыжок. Такой эффект является прямым следствием работы функции
PM_RecursiveHullCheck, которую мы разбирали в статье про физику pixelwalk: если начальная точка трейса оказалась к плоскости браша ближе, чем на
eps
, то подобраться ещё ближе функция ей не даст, но и отодвигать назад на границу
eps
-области также не будет.
Двигаемся дальше. На примере карты
hull_legacy.bsp
мы увидели, что тип legacy благодаря наклонным поверхностям значительно влияет на высоту блока, на который можно запрыгнуть. К примеру, плоскость под 45° дала нам запас в 9.96 юнита. Значит, нижнюю часть блока можно было поднять до 72 юнитов, а до 73 на первый взгляд не хватает даже с учётом зазора
eps
между моделькой игрока и полом. Но на самом деле при определённых условиях запрыгнуть на эту высоту всё же можно:
Нарисовав розовые
eps
-области вокруг clip-нодов, мы обнаруживаем себя в ситуации, аналогичной трейсу для pixelwalk. Разве что пересечение
eps
-областей даёт здесь не красный квадрат, а красный ромб, и благодаря этому трейс из начальной точки
p1
, не дотягивающей до уровня 73 + 18 юнитов (18 прибавляем потому, что говорим о взаимодействии центра игрока с clip-нодами для сидячей модельки), всё же пересекает и вертикальную, и наклонную плоскости. Ну а дальше уже знакомая нам ситуация - если clip-нод с вертикальной плоскостью встретился при обходе clip-дерева раньше, чем clip-нод с наклонной плоскостью, то в результирующий трейс запишется наклонная поверхность, а значит запрыгнуть у нас получится.
Для демонстрации этого явления я создал карту
hull_73_legacy.bsp
типа legacy (скачать можно
здесь), на которой расположил браш высотой 74 юнита и сделал два скоса под 45° высотой в 1 юнит на двух противоположных рёбрах:
На рисунке подписаны индексы clip-нодов, соответствующих плоскостям блока. Полностью clip-дерево карты для сидячего игрока выглядит следующим образом:
Как видим, clip-нод #36 при обходе дерева встретится позже, чем #33, поэтому запрыгнуть на блок спереди у нас получится. Кроме того, clip-нод #37 встретится позже, чем #34, а значит с задней стороны на блок не запрыгнуть.
А как дела в подобной ситуации обстоят у типа simple? Конечно, настолько же высоко, как на legacy, запрыгнуть не выйдет, но высота в 63 юнита ведь должна браться? Оказывается, здесь тоже не всё так очевидно. Посмотрим на тестовую карту
hull_63_simple.bsp
c cliptype типа simple (скачать можно
здесь). На ней я создал браш высотой 64 юнита и сделал точно такие же скосы, как в предыдущем примере. В итоге я получил схожее clip-деверо, и спереди на блок можно запрыгнуть без проблем, а вот с задней стороны... Казалось бы, начало трейса здесь, как и в предыдущем примере, попадает в красный ромб на пересечении
eps
-областей, и порядок clip-нодов в дереве указывает нам на то, что запрыгнуть не получится. Однако на самом деле у нас есть два способа обойти это. Поскольку в верхней точке прыжка мы находимся на высоте выше 63 + 18 юнитов, то во-первых мы можем разогнаться и прыгнуть в сторону блока, надеясь на то, что благодаря горизонтальной скорости ни одна точка трейса не попадёт в
eps
-область, и мы просто проскочим её, так что следующий трейс встретит только наклонную поверхность:
Другой способ – подойти вплотную к блоку лицом к нему, прыгнуть с зажатой
W, а затем в процессе прыжка зажать ещё и
D либо
A. Такая нехитрая манипуляция позволяет за счёт работы функции
PM_AirAccelerate (мы разбирали её в статье про
физику стрейфов) уменьшить составляющую скорости в направлении блока настолько, что в какой-то момент конечная точка трейса оказывается внутри
eps
-области, а значит пересечения плоскости clip-нода нет, и мы продвигаемся ближе к блоку. Таким образом мы постепенно проходим сквозь
eps
-область и в верхней точке прыжка переходим за её границы:
Собственно говоря, именно с такого блока начинается знаменитая карта
holy_lame. А это, кстати, сразу подсказывает нам, что при компиляции использовался cliptype типа simple.
Конец?