Изменение скина у модели оружия
Если вы играли в HL:Invasion, то наверное заметили, что некоторые пушки там имеют интересные свойства. Например экранчик у ракетницы при наводе на цель сменяет надпись с "No target" на "Locking in progress", а затем и вовсе на "Target locked". А так называемый "supergun" при выстрелах постепенно меняет свой цилиндр со светло-синего на ярко желтый. Как вы уже наверное догадались, это достигается сменой скинов у модели оружия. Умело используя эту фишку можно добиться очень интересных и впечатляющих эффектов. И я уверен, многие начинающие кодеры хотели бы ее заюзать.
Ну что же давайте попробуем это сделать. Как все знают смена боди достигается простой вставкой строчки:
pev->body = 1;
(это справедливо для чисто серверных оружий, в клиентских боди можно менять из эвента). Логично было бы предположить, что поставив строчку
pev->skin = 1;
мы так же легко поменяем и скин на оружии. Итак ставим такую строчку, например в примари аттак и... ничего не происходит. Опытному кодеру не составит труда быстро написать код для смены скинов, а вот новичку, который порой приходит в священный трепет от слова "клиент", это представляет практичски неразрешимую задачу. Туториалов посвященных этой теме я не видел, поэтому и решил сам написать подобный тутор.
Итак, приступим.
Менять скин мы будем в клиенте, поскольку на сервере это сделать не получиться.
Для того чтобы поменять скин клиент должен собсно знать номер скина, а следовательно, он должен как-то получить эту информацию. Для передачи информации в клиент у нас есть два способа - EVENT и MESSAGE. На ум сразу приходит, что сделать передачу эвентом намного проще - задекларировал эвент, прекешил его и передал - всего навсего три строчки на сервере. Да и принять его на клиенте - пара пустяков - пропишем его в hl_events.cpp и в ev_hldm.cpp и готово. Но эвент, передает кучу бесполезной информации - свое имя, положение игрока, флаги. А нам-то всего навсего надо передать один-единственный байтик - номер скина. Поэтому я решил остановиться на варианте с MESSAGE.
1.Подготовка MESSAGE на сервере.
Как известно MESSAGE (сообщение) для начала нужно задекларировать, для вызова из других функций. Для этого откроем player.h и добавим туда строчку нашего мессаджа, где нибудь в самом конце.
extern int gmsgSetSkin;
Теперь нам нужно создать сам мессадж - откроем player.cpp и найдем строчку LinkUserMessages и чуть выше добавим:
int gmsgSetSkin = 0;
Теперь опустимся в саму функцию LinkUserMessages и в самом ее конце, перед закрывающей скобкой зарегистриуем наше сообщение.
gmsgSetSkin = REG_USER_MSG("SetSkin", 1);
Теперь клиент готов к приему нашего мессаджа :)
2.Прием MESSAGE на клиенте.
Для начала стоит сказать, что мессаджи инициализируются вместе с худом, следовательно большую часть работы нам предстоит проделать именно в hud.cpp. Но сперва мессагу, равно как и ее переменную нужно задекларировать. Откроем hud.h и в объявлении класса CHUD, в секции public: добавим новую переменную
int m_iSkin;
Теперь спустимся по файлу до комментария // 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 ) и добавьте там вот эту строчку.
HOOK_MESSAGE( SetSkin );
Ну вот собсно и все - наш мессадж полностью настроен и готов к передаче\приему информации :)
3.Команда смены скина на сервере.
Чтобы не изобретать лишний раз велосипед, давайте так и оставим pev->skin для смены скина - главное его достоинство, то что он входит в структуру entvars_t и для его сохранения нам не нужно ничего делать - он сохраниться автоматически (ведь вы же не хотите, чтобы ваш скин пропадал при save\load). Теперь давайте подумаем - откуда удобнее всего послать наш мессадж ? Из 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();
Теперь, чтобы сменить скин достаточно прописать в коде оружия строчку
pev->skin = 1;
В любой функции, где вам это нужно. :)
4.Смена скина на клиенте.
Ну вот мы и подобрались к самому главному - собсно к смене скина на клиенте. Откроем hud_msg.cpp и в самом конце добавим следующую функцию:
void CHud :: MsgFunc_SetSkin( const char *pszName, int iSize, void *pbuf ) { BEGIN_READ( pbuf, iSize );//начинаем читать мессадж gHUD.m_iSkin = READ_BYTE();//присваиваем переменной m_iSkin значение, полученное из мессаджа cl_entity_s *view = gEngfuncs.GetViewModel();//Получаем "имя" текущей пушки view->curstate.skin = gHUD.m_iSkin;//меняем ей скин :) }
В принципе переменная m_iSkin не так уж и нужна - можно обойтись и без нее, но если вы захотите проверить текущий скин, например из эвента, то она вам очень даже пригодиться ;)
На этом все. Юзайте на здоровье :)
Дополнение 1.
Если вы подобно Xwiderу озабочены тем каждым байтом, который сервер посылает клиенту, и вам не нравиться, что мессага посылается несколько сот раз в секунду (хотя я не заметил, что бы траффик вообще изменился, в моменты бездействия) - можете для успокоения совести сделать проверку на изменение скина - для того чтобы, сообщение посылалось только в момент изменения скина.
Для этого в класс CBasePlayerWeapon добавим новую переменную(в weapons.h):
int m_iLastSkin;
А в секции сохранения - строчку, для сохранения этой переменной в функцию
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
|