Статьи по программированию под движком Gold Source
(игры Half-Life 1) и работе с SDK
Изменение скингруппы у модели оружия
Вступление
1. Подготовка MESSAGE на сервере
2. Приём MESSAGE на клиенте
3. Команда смены скингруппы на сервере
4. Смена скингруппы на клиенте
Дополнение
Вступление
Если вы играли в мод для игры Half-Life 1 HL:Invasion,
то наверное заметили, что некоторые виды оружия там имеют
интересные свойства.
Например экранчик у ракетницы при наводе на цель сменяет надпись с
"No target" на "Locking in progress", а затем и
вовсе на "Target locked". А так называемый
"supergun" при выстрелах постепенно меняет свой
цилиндр со светло-синего на ярко-жёлтый. Как вы
уже наверное догадались, это достигается сменой
текстур (скингрупп) у модели оружия. Умело используя эту
возможность, можно добиться очень интересных и
впечатляющих эффектов. И я уверен, многие
начинающие программисты под движком Gold Source
хотели бы её задействовать.
Ну что же, давайте попробуем это сделать.
Как все знают, смена сеточной группы (бодигруппы) достигается простой
вставкой строчки:
(это справедливо для чисто серверных оружий, в
клиентских бодигруппы можно менять с помощью ЭВЕНТа).
Логично было бы предположить, что поставив
строчку
мы так же легко поменяем и скингруппу на оружии. Итак ставим
такую строчку, например в первичную атаку... но
ничего не происходит. Опытному программисту не
составит труда быстро написать код для смены
скингрупп, а вот новичку, который порой приходит в
священный трепет от слова "клиент движка", это
представляет практически неразрешимую задачу.
Руководств, посвящённых этой теме, я не видел,
поэтому и решил сам написать статью.
Итак, приступим.
Менять скингруппы мы будем в клиенте, поскольку на сервере
это сделать не получиться.
Для того, чтобы
поменять текстуры, клиент должен собственно знать номер
скингруппы, а следовательно, он должен как-то
получить эту информацию.
Для передачи
информации в клиент у нас есть два способа —
EVENT и MESSAGE. На ум сразу приходит, что
сделать передачу событием (EVENT'ом) намного проще —
задекларировал EVENT, прекешил его и передал —
всего навсего три строчки на сервере. Да и
принять его на клиенте — пара пустяков —
пропишем его в файл hl_events.cpp и в ev_hldm.cpp и
готово. Но EVENT передает кучу бесполезной
информации — своё имя, положение игрока, флаги.
А нам-то всего навсего надо передать
один-единственный байтик — номер скингруппы. Поэтому
я решил остановиться на варианте с помощью MESSAGE.
1. Подготовка MESSAGE на сервере
Как известно MESSAGE
(сообщение) для начала нужно задекларировать
для вызова из других функций. Для этого откроем
файл player.h и добавим туда строчку нашего МЕССЕДЖа,
где-нибудь в самом конце.
Теперь нам нужно создать сам MESSAGE. Откроем файл
player.cpp и найдём строчку
LinkUserMessages
и чуть выше добавим:
Теперь опустимся в саму функцию LinkUserMessages
и в самом её конце перед закрывающей скобкой
зарегистриуем наше сообщение:
gmsgSetSkin = REG_USER_MSG("SetSkin", 1);
Теперь клиент готов к приёму нашего МЕССЕДЖа :).
2. Приём MESSAGE на клиенте
Для начала стоит сказать,
что MESSAGE инициализируются вместе с HUD-ом,
следовательно большую часть работы нам предстоит
проделать именно в файле hud.cpp. Но сперва сообщение,
равно как и его переменную, нужно задекларировать.
Откроем hud.h и в объявлении класса CHUD, в
секции public: добавим новую переменную
Теперь спустимся по файлу до комментария
// user
messages и добавим туда ещё одну "мессагу".
void _cdecl MsgFunc_SetSkin( const char *pszName, int iSize, void *pbuf );
Это перед комментарием // Screen information.
Теперь нам собственно нужно активировать сообщение при его
приёме. Добавьте в файл hud.cpp эту функцию (к
остальным МЕССЕДЖам, хотя положение этой функции
в файле не имеет значения).
int __MsgFunc_SetSkin(const char *pszName, int iSize, void *pbuf)
{
gHUD.MsgFunc_SetSkin( pszName, iSize, pbuf );
return 1;
}
Ну и осталось задекларировать сообщение на клиенте,
чтобы клиент движка знал, что оно существует, и мог его
корректно принять. Найдите в файле hud.cpp функцию
void CHud :: Init( void )
и добавьте там вот эту строчку.
Ну вот собственно и всё — наш MESSAGE полностью настроен и
готов к передаче и приёму информации :)
3. Команда смены скингруппы на сервере
Чтобы не изобретать лишний
раз велосипед, давайте так и оставим
pev->skin
для смены текстурной группы — главное его достоинство, то что он входит в структуру
entvars_t,
и для его сохранения нам не нужно
ничего делать — он сохраниться автоматически
(ведь вы же не хотите, чтобы ваша текстура изменялась на исходную
при сохранении/загрузке игры). Теперь давайте подумаем — откуда
удобнее всего послать наш MESSAGE? Из
SendWeapponAnim?
Но скингруппа — это вам не бодигруппа, и
её порой нужно изменять независимо от анимации,
а иногда и вовсе когда анимация стоит на
месте. А какая функция у оружия всё время
вызывается? Правильно — WeaponIdle.
Но пихать для каждого оружия три строчки МЕССЕДЖа —
согласитесь, это как-то муторно и неудобно.
Гораздо удобнее просто использовать для этого
уже существующую функцию, например,
ResetEmptySound
— она вызвается почти в любом оружии — кроме всяких там снарков, дистанционно управляемых взрывчаток и
гранат — ну короче, кроме того оружия, в котором не
нужно проигрывание звука щелчка бойка. Но естественно
добавить туда эту функцию — пара пустяков, а на код
оружия она не повлияет.
Откроем файл weapons.cpp
и найдём функцию
CBasePlayerWeapon
:: ResetEmptySound и добавим после строчки:
m_iPlayEmptySound = 1;
наш МЕССЕДЖ:
// update weapon skin from this
MESSAGE_BEGIN( MSG_ONE, gmsgSetSkin, NULL, m_pPlayer->pev );
WRITE_BYTE( pev->skin ); // weaponmodel skin.
MESSAGE_END();
Теперь, чтобы сменить текстуру достаточно прописать в коде
оружия строчку
в любой функции, где вам это нужно :) .
4. Смена скингруппы на клиенте
Ну вот мы и
подобрались к самому главному — собственно к смене
скингруппы на клиенте. Откроем файл hud_msg.cpp и в самом
конце кода добавим следующую функцию:
void CHud :: MsgFunc_SetSkin( const char *pszName, int iSize, void *pbuf )
{
BEGIN_READ( pbuf, iSize ); // начинаем читать MESSAGE
gHUD.m_iSkin = READ_BYTE(); // присваиваем переменной m_iSkin значение, полученное из MESSAGE
cl_entity_s *view = gEngfuncs.GetViewModel(); // Получаем "имя" текущей "пушки"
view->curstate.skin = gHUD.m_iSkin; // меняем ей скин :)
}
В принципе переменная m_iSkin
не так уж и нужна — можно обойтись и без неё, но если вы захотите
проверить текущую текстурную группу, например из события (из EVENT), то
она вам очень даже пригодится ;)
На этом всё. Используйте на здоровье :)
Дополнение
Если вы подобно XWiderу бережёте
каждый байт, который сервер посылает
клиенту, и вам не нравиться, что MESSAGE
посылается несколько сот раз в секунду (хотя я
не заметил, чтобы объём информации обмена (т.е. трафик) вообще изменился в
моменты бездействия) — можете для успокоения
совести сделать проверку на изменение скингруппы —
для того, чтобы сообщение посылалось только в
момент изменения текстуры.
Для этого в класс
CBasePlayerWeapon
добавим новую переменную (в файл weapons.h):
а в секции сохранения — строчку, для сохранения этой
переменной в функцию
CBasePlayerWeapon::m_SaveData[] =
DEFINE_FIELD( CBasePlayerWeapon, m_iLastSkin, FIELD_INTEGER ),
А саму функцию посылки МЕССЕДЖа немного доработаем
следующим образом:
if(m_iLastSkin != pev->skin)
{
// update weapon skin from this
MESSAGE_BEGIN( MSG_ONE, gmsgSetSkin, NULL, m_pPlayer->pev );
WRITE_BYTE( pev->skin ); // weaponmodel skin.
MESSAGE_END();
m_iLastSkin = pev->skin;
}
Теперь компилируйте сервер и сравнивайте, на сколько
байт уменьшился трафик :p (если он вообще
изменился).
g-cont
|