Author: TheTinySteini
Jaja, ich weiß, „Smokepuffs“ hört sich nicht so ganz jugendfrei an, aber ihr kennt sicher diese Rauchwolken aus CS, FLF und anderen Mods, die aufsteigen, wenn man gegen eine Wand schießt, oder? Und die heißen nunmal Smokepuffs. Sieht cool aus, also erfahrt ihr hier, wie man die Dinger selber macht.
Zuerst noch einige „Dankeschöns“, und zwar an: Omega, Adrian „Pink“ Finol, Jay Stelly, Yahn Bernier, rkzad, Prefect, die hlcoders-Mailingliste, die IRC-Channels #hl_coding und #wavelength und vor allem Christopher McArthur, von dem ich einigen Code habe.
So, nun aber zum eigentlich wichtigen Teil. Ich erklär erstmal das Grundprinzip, dann kommt der Code und dann ne „Einbauanleitung“.
Also, das Prinzip: Das ganze ist client-seitig ausgelegt, braucht also keinerlei bandbreitenbelastende Messages. Weil die Waffen auf Clientseite (auch dann wenn sich nur die Effekte dort abspielen) ja schon einen Auftreffpunkt berechnen, müssen wir uns um sowas nicht kümmern, wir übernehmen einfach den Endpunkt der vorhandenen Tracelines für unsere eigenen Berechnungen.
Zuallererst holen wir uns die Textur des Auftreffpunktes. Anhand dessen (und wenn der Mapper alle Texturen schön brav in die materials.txt eingetragen hat) kommen wir an das Material der Wand. Schließlich wollen wir andersfarbige Rauchwolken, wenn wir auf Sand schießen, als wenn wir auf Beton schießen. Und bei Metall werden gar keine Rauchwolken entstehen…
Wenn wir uns nun entschieden haben, dass Rauchwolken dargestellt werden sollen, dann holen wir uns erstmal die Normale des Auftreffpunktes. Die was? Die Normale, das heißt, ein Vektor senkrecht zur Fläche, auf der der Auftreffpunkt liegt. Netterweise wird uns dieser Vektor im Traceline-Ergebnis gleich mitgeliefert (pTrace→plane.normal). Diesen Normalenvektor lassen wir dann in die drei Vektoren forward, up und right umwandeln, so dass wir ganz einfach angeben können, wie stark die Rauchwolke nach vorne, zur Seite und in der Höhe beschleunigen soll. Dann kehren wir auch gleich forward.z um, sonst fliegt nämlich die Wolke in die Wand rein anstatt von ihr weg.
Ja, dann kommt eigentlich schon der wichtigste Teil, das eigentliche Erstellen des Sprites. Dazu rufen wir Engine-Funktion R_TempSprite auf. Als ersten Parameter geben wir den Entstehungsort des Sprites an, also einfach den Einschlagsort des Projektils. Der zweite Parameter ist die Beschleunigung. Hier basteln wir uns aus den Vektoren forward, up und right mit Hilfe des Zufalls immer wieder unterschiedliche Werte zusammen, damit nicht jeder Smokepuff gleich aussieht. Die Skalierung des Sprites geben wir im 3. Parameter an, 1.0 wäre Originalgröße, 2.0 doppelt so groß, 0.4 sind also 40% der Originalgröße. Nun brauchen wir noch den Index des zu verwendenden Sprites, diesen haben wir uns schon vorher mit EV_FindModelIndex geholt. Die nächsten 3 Angaben sind RenderMode, RenderFX und Alphawert. RenderMode muss für ein Sprite vom Typ „Indexalpha“ - wie ich und FLF es verwenden - auf kRenderTransAlpha stehen. RenderFX brauchen wir nicht, hier also kRenderFxNone. Weil wir den Alpha-Wert (also die Transparenz) später noch genauer einstellen, setzen wir ihn erstmal auf 1.0. Fehlen noch 2 Parameter. Zunächt brauchen wir noch die Lebensdauer des Sprites, bis der FadeOut beginnt (in Sekunden), und dann noch die Flags. Ich hab hier FTENT_SPRANIMATE und FTENT_FADEOUT gesetzt, damit das Sprite 1. animiert ist und 2. sich nach der Lebensdauer langsam in Luft auflöst.
Wenn die Erstellung des Sprites erfolgreich war, sprich pTemp nicht NULL ist, dann setzen wir die Ausblendegeschwindigkeit, die Framerate sowie Transparenz und Farbe des Sprites ein. Und das war's dann auch schon!
Zusätzlich habe ich noch ein bisschen Code eingebaut, mit dem man an der Einschlagstelle Funken entstehen lassen kann. Dadurch kann man schnell und einfach die gewünschten Effekte bei den Texturtypen einstellen.
Ok, genug der Vorrede, hier kommt der Code:
/* TTT: Event which spawns a smokepuff and/or sparks at a given origin Note that you have to precache the sprites in the game dll */ void EV_HLDM_SmokePuff( pmtrace_t *pTrace, float *vecSrc, float *vecEnd ) { physent_t *pe; // get entity at endpoint pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); if ( pe && pe->solid == SOLID_BSP ) { // if it's a solid wall / entity char chTextureType = CHAR_TEX_CONCRETE; char *pTextureName; char texname[ 64 ]; char szbuffer[ 64 ]; // get texture name pTextureName = (char *)gEngfuncs.pEventAPI->EV_TraceTexture( pTrace->ent, vecSrc, vecEnd ); if ( pTextureName ) { strcpy( texname, pTextureName ); pTextureName = texname; // strip leading '-0' or '+0~' or '{' or '!' if (*pTextureName == '-' || *pTextureName == '+') { pTextureName += 2; } if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') { pTextureName++; } // '}}' strcpy( szbuffer, pTextureName ); szbuffer[ CBTEXTURENAMEMAX - 1 ] = 0; // get texture type chTextureType = PM_FindTextureType( szbuffer ); } bool fDoPuffs = false; bool fDoSparks = false; int a,r,g,b; switch (chTextureType) { // do smoke puff and eventually add sparks case CHAR_TEX_TILE: case CHAR_TEX_CONCRETE: fDoSparks = (gEngfuncs.pfnRandomLong(1, 4) == 1); fDoPuffs = true; a = 128; r = 200; g = 200; b = 200; break; // don't draw puff, but add sparks often case CHAR_TEX_VENT: case CHAR_TEX_GRATE: case CHAR_TEX_METAL: fDoSparks = (gEngfuncs.pfnRandomLong(1, 2) == 1); break; // draw brown puff, but don't do sparks case CHAR_TEX_DIRT: case CHAR_TEX_WOOD: fDoPuffs = true; a = 250; r = 97; g = 86; b = 53; break; // don't do anything if those textures (perhaps add something later...) default: case CHAR_TEX_GLASS: case CHAR_TEX_COMPUTER: case CHAR_TEX_SLOSH: break; } if( fDoPuffs ) { vec3_t angles, forward, right, up; VectorAngles( pTrace->plane.normal, angles ); AngleVectors( angles, forward, up, right ); forward.z = -forward.z; // get sprite index int iWallsmoke = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/wallsmoke.spr"); // create sprite TEMPENTITY *pTemp = gEngfuncs.pEfxAPI->R_TempSprite( pTrace->endpos, forward * gEngfuncs.pfnRandomFloat(10, 30) + right * gEngfuncs.pfnRandomFloat(-6, 6) + up * gEngfuncs.pfnRandomFloat(0, 6), 0.4, iWallsmoke, kRenderTransAlpha, kRenderFxNone, 1.0, 0.3, FTENT_SPRANIMATE | FTENT_FADEOUT ); if(pTemp) { // sprite created successfully, adjust some things pTemp->fadeSpeed = 2.0; pTemp->entity.curstate.framerate = 20.0; pTemp->entity.curstate.renderamt = a; pTemp->entity.curstate.rendercolor.r = r; pTemp->entity.curstate.rendercolor.g = g; pTemp->entity.curstate.rendercolor.b = b; } } if( fDoSparks ) { // spawn some sparks gEngfuncs.pEfxAPI->R_SparkShower( pTrace->endpos ); } } }
Wo baut ihr das Ding nun ein? Am besten in die ev_hldm.cpp, direkt über die Funktion EV_HLDM_FireBullets(). Aufrufen könnt ihr den Effekt dann z.B. in besagter EV_HLDM_FireBullets, in dem switch(iBulletType)-Teil. Etwa so:
case BULLET_PLAYER_MP5: if ( !tracer ) { EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); EV_HLDM_DecalGunshot( &tr, iBulletType ); EV_HLDM_SmokePuff( &tr, vecSrc, vecEnd ); } break;
Ganz am Anfang der ev_hldm.cpp, aber nach den #includes, fügt ihr noch dies hier ein:
void VectorAngles( const float *forward, float *angles );
Als Sprite könnt ihr testweise das von FLF nehmen, das hab ich auch benutzt. Ihr kopiert es einfach in euer Modverzeichnis unter /sprites/wallsmoke.spr (oder wie auch immer, ihr müsst halt dann die Dateinamen im Code anpassen). Und nicht vergessen, das Sprite serverseitig zu precachen, entweder in der Precache-Funktion der jeweiligen Waffe oder direkt in der W_Precache, wenn ihr's sowieso bei (fast) jeder Waffe braucht. Wer nicht genau weiß, wie das geht, wirft nen Blick ins Waffentutorial.
Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.