Écrit par David Henry, le 1 septembre 2002
Attention : cet article a été écrit pour le SDK 2.2 d'Half-Life 1 ! Il se peut qu'il ne soit pas entièrement compatible avec des versions antérieures ou supérieures du Kit.
Ce tutorial a pour but de vous montrer de vous montrer comment créer
une arme de contact (crowbar, couteau, clef de 12, etc.) en modifiant la
fonction PrimaryAttack()
ou SecondaryAttack()
.
Nous supposeront dans ce tutorial que la classe de votre arme s'appelle
CMonArme
.
Pour déterminer l'objet que l'on touche lorsque l'on frappe avec notre
arme, on va utiliser la fonction FindHullIntersection()
. Cette
fonction existe déjà dans crowbar.cpp. Si vous comptez garder la
crowbar, vous pouvez simplement déclarer la fonction (en haut du fichier
de votre arme) :
// définition de fonction dans crowbar.cpp void FindHullIntersection (const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity);
Dans le cas contraire, il va falloir conserver cette fonction ailleurs. Le mieux est de l'ajouter comme fonction membre à la classe de votre arme :
///////////////////////////////////////////////////////////////////////////// // // CMonArme - classe pour le ma super arme. // ///////////////////////////////////////////////////////////////////////////// class CMonArme : public CBasePlayerWeapon { public: // ... void FindHullIntersection (const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity); };
Voici maintenant la définition de la fonction
FindHullIntersection()
:
// -------------------------------------------------------------------------- // FindHullIntersection // // Détermine le point d'impact de plus proche. // -------------------------------------------------------------------------- void FindHullIntersection (const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity) { float distance; float *minmaxs[2] = { mins, maxs }; TraceResult tmpTrace; Vector vecHullEnd = tr.vecEndPos; Vector vecEnd; distance = 1e6f; vecHullEnd = vecSrc + ((vecHullEnd - vecSrc) * 2); UTIL_TraceLine (vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace); if (tmpTrace.flFraction < 1.0) { tr = tmpTrace; return; } for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { vecEnd.x = vecHullEnd.x + minmaxs[i][0]; vecEnd.y = vecHullEnd.y + minmaxs[j][1]; vecEnd.z = vecHullEnd.z + minmaxs[k][2]; UTIL_TraceLine (vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace); if (tmpTrace.flFraction < 1.0) { float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length (); if (thisDistance < distance) { tr = tmpTrace; distance = thisDistance; } } } } } }
La fonction calcule le point d'intersection le plus proche du coup de lame (ou autre, tout dépend de votre arme).
Passons maintenant à la fonction PrimaryAttack()
(en admettant
que c'est elle qui gère les coups au corps à corps). Dans un premier temps,
on calcule une ligne partant du joueur vers où il regarde, puis s'il rencontre
un obstacle, on calcule le point d'intersection de la ligne et de l'entité.
Ensuite on déclenche l'event, puis on fait jouer une animation aux modèles de
l'arme et du joueur. On applique les dommages, et on fait jouer un son selon
le type d'objet percuté (et on oublie pas un ptit décal d'impact sur les murs).
// -------------------------------------------------------------------------- // CMonArme::PrimaryAttack // // Premier mode de tir. // -------------------------------------------------------------------------- void CMonArme::PrimaryAttack () { static int iSwing; int fDidHit = FALSE; TraceResult tr; // on trace une ligne du joueur vers l'objet qu'il regarde UTIL_MakeVectors (m_pPlayer->pev->v_angle); Vector vecSrc = m_pPlayer->GetGunPosition (); Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; UTIL_TraceLine (vecSrc, vecEnd, dont_ignore_monsters, ENT (m_pPlayer->pev), &tr); #ifndef CLIENT_DLL if (tr.flFraction >= 1.0) { UTIL_TraceHull (vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT (m_pPlayer->pev), &tr); if (tr.flFraction < 1.0) { // on calcule le point d'intersection de la ligne et de l'objet // qu'on a touché (approximation) CBaseEntity *pHit = CBaseEntity::Instance (tr.pHit); if (!pHit || pHit->IsBSPModel ()) FindHullIntersection (vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict ()); // ceci est le point sur la surface actuelle vecEnd = tr.vecEndPos; } } #endif // on appelle l'event PLAYBACK_EVENT (FEV_NOTHOST, m_pPlayer->edict (), m_usMonArme); // force le joueur à jouer l'animation "attack" m_pPlayer->SetAnimation (PLAYER_ATTACK1); if (tr.flFraction >= 1.0) { // manqué m_flNextPrimaryAttack = UTIL_WeaponTimeBase () + 0.5; } else { // on joue une des trois animations du modèle de l'arme switch (((iSwing++) % 2) + 1) { case 0: SendWeaponAnim (MA_ATTACK1HIT); break; case 1: SendWeaponAnim (MA_ATTACK2HIT); break; case 2: SendWeaponAnim (MA_ATTACK3HIT); break; } #ifndef CLIENT_DLL // touché fDidHit = TRUE; CBaseEntity *pEntity = CBaseEntity::Instance (tr.pHit); // on applique les domages ClearMultiDamage (); pEntity->TraceAttack (m_pPlayer->pev, gSkillData.plrDmgMonArme, gpGlobals->v_forward, &tr, DMG_CLUB); ApplyMultiDamage (m_pPlayer->pev, m_pPlayer->pev); // on joue le son approprié (vouwt, slpatch, dong) float flVol = 1.0; int fHitWorld = TRUE; if (pEntity) { if ((pEntity->Classify () != CLASS_NONE) && (pEntity->Classify () != CLASS_MACHINE)) { // on a touché quelque chose d'organique switch (RANDOM_LONG (0, 2)) { case 0: EMIT_SOUND (ENT (m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; case 1: EMIT_SOUND (ENT (m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; case 2: EMIT_SOUND (ENT (m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; } m_pPlayer->m_iWeaponVolume = 128; if (!pEntity->IsAlive ()) return; else flVol = 0.1; // ne pas jouer de son "obstacle bsp" fHitWorld = FALSE; } } // on joue un son selon la texture de l'obstacle heurté if (fHitWorld) { float fvolbar = TEXTURETYPE_PlaySound (&tr, vecSrc, vecSrc + (vecEnd-vecSrc) * 2, BULLET_PLAYER_MONARME); switch (RANDOM_LONG (0, 1)) { case 0: EMIT_SOUND_DYN (ENT (m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG (0, 3)); break; case 1: EMIT_SOUND_DYN (ENT (m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG (0, 3)); break; } } m_pPlayer->m_iWeaponVolume = flVol * 512 #endif m_flNextPrimaryAttack = UTIL_WeaponTimeBase () + 0.25; // on applique un décal d'impact sur le mur DecalGunshot (&tr, BULLET_PLAYER_MONARME); pev->nextthink = UTIL_WeaponTimeBase () + 0.2; } }
Voilà. J'ai encore quelques commentaires à apporter sur cette fonction :
iSwing
est déclarée comme statique car on a besoin de garder la
valeur du PrimaryAttack()
précédent (sinon elle ne servirait à rien).
Elle sert après à jouer toutes les animations « hit » en boucle (ici
on admet qu'il y'en a trois). fHitWorld
indique si l'on a heurté
un objet de la map ou une entité. Si c'est un bloc solide, on jouera un son
relatif à la texture de ce dernier.
Passez côté DLL client, ev_hldm.cpp pour adapter la fonction event à votre arme :
// -------------------------------------------------------------------------- // EV_MonArmeFire // // Fonction event de MonArme. // -------------------------------------------------------------------------- void EV_MonArmeFire (struct event_args_s *args) { static int iSwing; vec3_t origin; vec3_t angles; VectorCopy (args->origin, origin); // joue un son "dans le vent" gEngfuncs.pEventAPI->EV_PlaySound (args->entindex, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, PITCH_NORM); if (EV_IsLocal (args->entindex)) { // joue une animation "coup manqué" switch ((iSwing++) % 3) { case 0: gEngfuncs.pEventAPI->EV_WeaponAnimation (MA_ATTACK1MISS, 1); break; case 1: gEngfuncs.pEventAPI->EV_WeaponAnimation (MA_ATTACK2MISS, 1); break; case 2: gEngfuncs.pEventAPI->EV_WeaponAnimation (MA_ATTACK3MISS, 1); break; } } }
Rien de compliqué. La fonction event ici sert surtout à jouer les sons/animations lorsque le joueur frappe dans le vide.
Avant de clore ce tutorial, je voudrais vous rappeler quelques petits trucs pour ce genre d'arme. Tout d'abord, n'oubliez pas de précacher les sons utilisés :
// -------------------------------------------------------------------------- // CMonArme::Precache // // Précache les ressources nécessaires // -------------------------------------------------------------------------- void CMonArme::Precache () { // sons PRECACHE_SOUND ("weapons/cbar_hit1.wav"); PRECACHE_SOUND ("weapons/cbar_hit2.wav"); PRECACHE_SOUND ("weapons/cbar_hitbod1.wav"); PRECACHE_SOUND ("weapons/cbar_hitbod2.wav"); PRECACHE_SOUND ("weapons/cbar_hitbod3.wav"); PRECACHE_SOUND ("weapons/cbar_miss1.wav"); // ... }
Désactivez aussi les types de munitions pour le mode de tir utilisant le
corps à corps dans GetItemInfo()
:
p->pszAmmo1 = NULL; p->iMaxAmmo1 = -1; p->iMaxClip = WEAPON_NOCLIP;
Enfin, si votre arme se tiens à une main comme le pied de biche, dans
la fonction Deploy()
, passez en dernier paramètre de
DefaultDeploy()
la chaîne « crowbar » :
// -------------------------------------------------------------------------- // CMonArme::Deploy // // Déploiement de l'arme. // -------------------------------------------------------------------------- BOOL CMonArme::Deploy () { return DefaultDeploy ("models/v_monarme.mdl", "models/p_monarme.mdl", MA_DRAW, "crowbar"); }
Voilà, c'est fini. Vous pouvez également combiner corps à corps et arme à feu (par exemple, un coup de crosse en second mode de tir).
Cet article est mis à disposition sous un contrat Creative Commons (licence CC-BY-ND).