TraceLines
![Wikipedia - Letter.png](/w/images/7/7b/Wikipedia_-_Letter.png)
![Underlinked - Logo.png](/w/images/9/91/Underlinked_-_Logo.png)
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
January 2024
![Icon-broom.png](/w/images/thumb/7/71/Icon-broom.png/40px-Icon-broom.png)
You can help by
![Wikipedia icon](/w/images/thumb/9/9a/Wikipedia_icon_small.png/16px-Wikipedia_icon_small.png)
January 2024
Трассировка это процесс следования из точки A по направлению в точку B и нахождения первого объекта, который "попался" на пути из A в B (TraceLine). Самый простой пример использования трассировщика - в оружии. Оружие использует трассировщики для определения места попадения в кого-либо и произошло ли попадение.
Линия начинается от дула оружия (точка A) и заканчиваетсяв произвольной точке на расстоянии 8000 или больше юнитов перед ней. Если она попадает в мир (стена, ящик, что-нибудь твердое), то код сообщает ему, что нужно отобразить эффект (например, облако дыма исходящее из стены, или искры от металла...). Если линия попадает в игрока, она наносит повреждения.
Contents
Получение начальной и конечной точек
Здесь приводится несколько функций, которые мы первым делом должны знать для применения трассировки. Обычно трассировщики используют игроков, если сказать более обобщенно - энтити.
GetAbsOrigin(): Указывает на точку, лежащую "у ног" игрока. Для многих энтитей-моделей, это также является "низом" модели, или иногда центром.
Weapon_ShootPosition(): Только для класса игрока. Это точка откуда начинается выстрел оружия. Позиция приблизительная. Если вам требуется отображать эффекты (например трассеры от пуль, видимые линии выходящие из ствола оружия) используйте функцию приведенную ниже:
GetAttachment() и LookupAttachment(): GetAttachment() возвращает позицию присоединения (attachment point) на модели энтити. Точка аттачмента является точкой на модели, определенную моделлером в QC файле. Вы можете просмотреть список точек аттачмента для моделей при помощи утилиты HLMV . Список нумеруется с нуля. LookupAttachment() принимает в качестве аргумента char* (стрка с именем аттачмента) и возвращает номер соответствующего аттачмента, или -1 если он не найден.
Теперь, когда мы имеем "точку A" требуется определить "точку B".
Source SDK предоставляет много мощных функций для преобразования углов в векторы. Что попросту называется поворотом на "45 по вертикали, 90 по горизонтали, 0 вращения", можно представить в виде вектора направленного влево и вверх на 45 градусов. Функция AngleVectors(), перегруженная как принимающая в качестве параметров один QAngle и один указатель на Vector, и как QAngle и три указателя на Vector. Функция возвращает векторы: передний (forward), который направлен по направлению, указываемому углами, верхний (up), который повернут на 90 градусов "вверх", и правый, который повернут на 90 градусов "вправо" от переднего.
AngleVectors следует используют в сочетании с следующими функциями:
EyeAngles(): Только для игроков. Возвращает текущие углы зрения.
GetAbsAngles(): Для игроков, возвращает направление в котором направлена их модель (здесь нужно быть осторожным, обычно эти значения не синхронизируются с EyeAngles, потому что модель игрока поворачивается только в пределах увеличения на 15 или 30 градусов так что это не выглядит пугающе). Для других энтитей, это их ориентация.
Теперь, когда мы знаем как получить точку A, и направление, теперь дело за простым математическим расчетом по существующей оси направления до точки B - "точка A + направление * X," где X это расстояние на которое требуется трассировка.
Объекты которым НЕОБХОДИМО попадание, например след от пули, значение X довольно большое (часто MAX_TRACE_LENGTH
, определенное в worldsize.h
), итак мы будем почти всегда попадают во что-то (стена где-нибудь на карте, skybox, в далёких игроков и т.д.).
Самотрассировка
Существует два набора параметров которые возможно передавать трассировщикам, один из них TraceFilter, который не особенно часто используется, рассмотрим второй из них:
UTIL_TraceLine( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, const IHandleEntity *ignore, int collisionGroup, trace_t *ptr )
Рассмотрим аргументы этой функции:
const Vector& vecAbsStart: точка A. "const" здесь указывает что значение не может изменяться внутри функции.
const Vector& vecAbsEnd: точка B. Также не меняется в функции.
unsigned int mask: Этот аргумент очень интересен. Это целое число (int) является битовой маской bitmask указывающей что требуется трассировать для попадения. Список определений для масок (MASK) находятся в файле bspflags.h. Наиболее часто используемыми из которых являются MASK_SHOT, MASK_SOLID_BRUSHONLY и MASK_SOLID. Вы можете также создавать ваши собственные маски, комбинируя различные CONTENTS_ флаги.
const IHandleEntity *ignore: Указатель на энтить которую следует игнорировать. Для собственной энтити можно передать указатель на CBaseEntity, это будет работать со всеми энтитями. Можно использовать указатель "this" если трассировка производится изнутри модели энтити, в таком случае трассировка приведет "на выходе" из собственной геометрии. Если в вашем случае трассировка не должна быть прервана прилегающим объектом, использйте в этом аргументе энтить игнорирования.
int collisionGroup: Этот аргумент указывает, какая группа коллизии должна подвергаться трассировке, константы определены в const.h. Определенные группы могут попадать только в другие определённые группы. Установив COLLISION_GROUP_NONE приведет к трассировке попадания во всё (это известно по опыту).
trace_t *ptr: Указатель на класс трассировщика (как может показаться что это структура, но это на самом деле класс!) который должен быть заполнен. Проще говоря это "таблица результатов" получаемая в результате трассировки. Просто определите: "trace_t tr;" и передайте в эту функцию "&tr"...
Анализ и использование результатов
Отлично, теперь мы произвели трассировку, что теперь?! Как было только-что сказано выше, мы будем использовать результаты из указателя trace_t. Теперь, это то что потребуется для получения данных о том как прошла трассировка, и если возможно, извлечь данные из него. Не будем рассматривать каждую функцию и переменную trace_t, рассмотрим только наиболее часто используемые. Детальнее можете самостоятельно рассмотреть в объявлении trace_t или CGameTrace.
startpos, endpos: startpos это точка A, endpos это то место где трассировка натолкнулась на что-то и остановилась. В нашем примере это точка, где пуля попала в стену. Нужно обратить внимание, это та точка в которой произошло попадение, а не несколько юнитов спереди или позади, так что если понадобиться породить что-либо в этом месте, нужно убедиться в наличии пространства для него.
fraction: Это вещественное значение в пределах от 0 до 1 выражающее расстояние в процентах между точкой A и B "пройденное" до момента попадания. Важно по разным причинам, главная из них это условие если tr.fraction == 1.0, то трассировка не попала ни во что, и если tr.fraction != 1.0, трассировка попала во что-то.
startsolid и allsolid: startsolid это булевое значение, означающее что трасса "внутри" чего-либо (обычно мир) в месте начала (точка A), allsolid означает что точка B "внутри".
plane: Это информация о поверхности попадания. Нужно обратить внимание, если allsolid равен true, это означает что плоскость не действительна. plane содержит структуру с большим количеством информации о плоскости попадания. Например plane.normal - нормаль к поверхности плоскости попадания, которая может использоваться для разных целей, например "отражение" от точки попадания используя endpos + plane.normal*количество юнитов от плоскости.
surface: Это информация о месте попадения, включает имя поверхности, материалы (props) , если это указано в материале, и наиболее вжное: флаги. Вы можите найти список флагов поверхностей в файле publicbspflags.h. Один из важнейших - SURF_SKY, который означает поверхность скайбокса. Он важен для нескольких целей, например, если вы не хотите анимировать эффекты от попадения в небо, это есть способ определить было попадение в небо или нет.
m_pEnt: О, это наиболее важно. Это энтить в которую было попадение! Обычно если это мир значение будет NULL, также NULL будет в случае если попадения не было, поэтому следует сначала проверять его перед использованием. Это может быть использовано для многих целей, но обычно наиболее важная - определение: что это за объект? CBaseEntity имеет несколько функций, которые могут помочь в этом:
IsXXXX(): IsPlayer(), IsNPC(), и т.д. Название говорит само за себя. Все эти методы определены в CBaseEntity.h и переопределены в проризводных классах.
GetClassname(): Возвращает имя энтити, в которую было попадение. На стороне сервера, обычно это будет имя указанное в LINK_ENTITY_TO_CLASS(), но на клиенте, это имя будет отображаться, если энтитя была предсказательной (predictable) - тоесть, отображаемая на клиенте. В противном случае функция возвращает настоящее имя класса, которое выглядит например как "class C_BaseMyClass". Если нет уверенности в имени определенной энтити, можно предварительно проверить его при помощи Warning(), выведя в консоль имя класса всех энтитей, которые окружают игрока (можно сделать для этого собственную консольную команду, которая выполняет это на стороне клиента) после чего далее можно производить корректные действия.
GetTeamNumber(): Это еще одна важная функция: номер команды (team), в которой находится энтитя. Для не-игроков, это обычно значение, равное UNASSIGNED, а в других случаях - если энтити связаны с игроком (оружие, гранаты, и т.д.).
Пример
Эта трассировка из глаз игроков к самой дальней возможной дистанции (MAX_TRACE_LENGTH ) пытаясь попасть в NPCs по пути.
CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); // подготавливаем вектора и трасировщики trace_t tr; Vector vecStart, vecStop, vecDir; // получаем углы AngleVectors( pPlayer->EyeAngles( ), &vecDir ); // получаем вектора vecStart = pPlayer->EyePosition(); vecStop = vecStart + vecDir * MAX_TRACE_LENGTH; // делаем трассировку UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NPC, &tr ); // проверяем видим ли мы NPC if ( tr.m_pEnt ) { if ( tr.m_pEnt->IsNPC() ) { // да, мы попали в NPC! } }