Shambler team
Статьи по программированию под движком 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 хотели бы её задействовать.

Ну что же, давайте попробуем это сделать. Как все знают, смена сеточной группы (бодигруппы) достигается простой вставкой строчки:

		pev->body = 1;

(это справедливо для чисто серверных оружий, в клиентских бодигруппы можно менять с помощью ЭВЕНТа). Логично было бы предположить, что поставив строчку
	pev->skin = 1;

мы так же легко поменяем и скингруппу на оружии. Итак ставим такую строчку, например в первичную атаку... но ничего не происходит. Опытному программисту не составит труда быстро написать код для смены скингрупп, а вот новичку, который порой приходит в священный трепет от слова "клиент движка", это представляет практически неразрешимую задачу. Руководств, посвящённых этой теме, я не видел, поэтому и решил сам написать статью.

Итак, приступим.
Менять скингруппы мы будем в клиенте, поскольку на сервере это сделать не получиться.

Для того, чтобы поменять текстуры, клиент должен собственно знать номер скингруппы, а следовательно, он должен как-то получить эту информацию.

Для передачи информации в клиент у нас есть два способа — EVENT и MESSAGE. На ум сразу приходит, что сделать передачу событием (EVENT'ом) намного проще — задекларировал EVENT, прекешил его и передал — всего навсего три строчки на сервере. Да и принять его на клиенте — пара пустяков — пропишем его в файл hl_events.cpp и в ev_hldm.cpp и готово. Но EVENT передает кучу бесполезной информации — своё имя, положение игрока, флаги. А нам-то всего навсего надо передать один-единственный байтик — номер скингруппы. Поэтому я решил остановиться на варианте с помощью MESSAGE.

1. Подготовка MESSAGE на сервере

Как известно MESSAGE (сообщение) для начала нужно задекларировать для вызова из других функций. Для этого откроем файл player.h и добавим туда строчку нашего МЕССЕДЖа, где-нибудь в самом конце.

	extern int	gmsgSetSkin;

Теперь нам нужно создать сам MESSAGE. Откроем файл player.cpp и найдём строчку LinkUserMessages и чуть выше добавим:
	int gmsgSetSkin = 0;

Теперь опустимся в саму функцию LinkUserMessages и в самом её конце перед закрывающей скобкой зарегистриуем наше сообщение:
	gmsgSetSkin = REG_USER_MSG("SetSkin", 1);

Теперь клиент готов к приёму нашего МЕССЕДЖа :).


2. Приём MESSAGE на клиенте

Для начала стоит сказать, что MESSAGE инициализируются вместе с HUD-ом, следовательно большую часть работы нам предстоит проделать именно в файле 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 );

Ну вот собственно и всё — наш 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();

Теперь, чтобы сменить текстуру достаточно прописать в коде оружия строчку
	pev->skin = 1;

в любой функции, где вам это нужно :) .


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):

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
2004 Shambler Team
  Counter.CO.KZ -     !
Hosted by uCoz