Ajouter du brouillard sur une map

Écrit le 03/07/2003 par Bicou
Dernière mise à jour : 30/01/2006

Introduction

Me voilà de retour pour un nouveau tutorial, ce coup-ci c'est pour créer un env_fog sur votre map. Si vous êtes pas convaincus ou que vous savez pas ce que c'est :

http://www.game-lab.com/images/tuts/hl1_fog_opengl/1.jpg http://www.game-lab.com/images/tuts/hl1_fog_opengl/2.jpg

Le fog OpenGL dans une map

Le problème c'est que ça ne marche qu'en mode OpenGL :`(

Comprendre le tut

Il existe, dans les bibliothèques de fonctions de l'OpenGL, une fonction qui permet d'afficher un fog dans un moteur 3D. Grâce au nouveau SDK (2.2 et plus), valve nous permet d'utiliser ces fonctions avec le moteur de HL, donc dans votre mod vous pourrez afficher ce fog, et même utiliser toutes les fonctions OpenGL que vous connaissez.

Nous allons d'abord créer une entité qui va nous permettre de mettre un env_fog dans une map (dans Worldcraft) et de configurer ses paramètres (couleur, etc.). Ensuite on va créer un message qui ne sera envoyé qu'une fois, qui va dire au client quelle est la couleur du fog, et les différents paramètres. Le client va récupérer le message par l'intermédiaire d'une classe dérivée de ChudBase, pour faire du code propre, et à chaque frame, dans la fonction Draw() de votre classe CHudBase, vous allez lancer les fonctions OpenGL qui permettent donc l'affichage du fog.

Côté serveur

Donc là c'est très simple, on crée une nouvelle entité. On crée tout d'abord sa classe, allez dans cbase.h (eh oui, faudra beaucoup compiler :{), et tout à la fin du fichier :

//Bicou
class CClientFog : public CBaseEntity
{
public:
    void Spawn( void );
    void KeyValue( KeyValueData *pkvd );
    float m_iStartDist;
    float m_iEndDist;
};
//Bicou -fin

Voilà, on a notre classe, maintenant faut l'implémenter (la définir complètement). Allez dans triggers.cpp, tout à la fin du fichier :

//Bicou
void CClientFog :: KeyValue( KeyValueData *pkvd )
{
    if (FStrEq(pkvd->szKeyName, "startdist"))
    {
        m_iStartDist = atoi(pkvd->szValue);
        pkvd->fHandled = true;
    }
    else if (FStrEq(pkvd->szKeyName, "enddist"))
    {
        m_iEndDist = atoi(pkvd->szValue);
        pkvd->fHandled = true;
    }
    else
    {
        CBaseEntity::KeyValue( pkvd );
    }
}

void CClientFog :: Spawn ( void )
{
    pev->movetype = MOVETYPE_NOCLIP;
    pev->solid = SOLID_NOT;    // Remove model & collisions
    pev->renderamt = 0;        // The engine won't draw this model if this
                               // is set to 0 and blending is on
    pev->rendermode = kRenderTransTexture;
}

LINK_ENTITY_TO_CLASS( env_fog, CClientFog );
//Bicou -fin

Ca y est, l'entité est créée. C'est un env_fog. Maintenant faut dire au client qu'on veut qu'il affiche le fog. On va créer un nouveau message. Direction player.cpp, au début du fichier :

int gmsgTextMsg = 0;
int gmsgSetFOV = 0;
int gmsgShowMenu = 0;
int gmsgGeigerRange = 0;
int gmsgTeamNames = 0;
int gmsgFog = 0; //Nouveau code

void LinkUserMessages( void )
{
    // Already taken care of?
    if ( gmsgSelAmmo )

Et un peu plus bas :

    gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 );
    gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake));
    gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade));
    gmsgAmmoX = REG_USER_MSG("AmmoX", 2);
    gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 );
    //FOG:
    gmsgFog = REG_USER_MSG( "Fog", 7 );
}

LINK_ENTITY_TO_CLASS( player, CBasePlayer );

Bon ça y est on a notre message, mais maintenant faut l'envoyer. Toujours dans player.cpp, ligne 3700 et des brouettes (dans la fonction CBasePlayer::UpdateClientData)

        if ( !m_fGameHUDInitialized )
        {
            MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev );
            MESSAGE_END();

            g_pGameRules->InitHUD( this );
            m_fGameHUDInitialized = true;

            //BICOU -debut

            CBaseEntity *pEntity = NULL;
            pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
                       
            if ( pEntity )
            {
                ALERT( at_console, "Map has fog!\n" );
                CClientFog *pFog = (CClientFog *)pEntity;

                MESSAGE_BEGIN( MSG_ONE, gmsgFog, NULL, pev );
                    WRITE_BYTE( pFog->pev->rendercolor.x );
                    WRITE_BYTE( pFog->pev->rendercolor.y );
                    WRITE_BYTE( pFog->pev->rendercolor.z );
                    WRITE_SHORT( pFog->m_iStartDist );
                    WRITE_SHORT( pFog->m_iEndDist );
                MESSAGE_END();
            }
            else
            {
                ALERT( at_console, "Map doesn't have any fog!\n" );
            }
            //Bicou -fin

            if ( g_pGameRules->IsMultiplayer() )
            {
                FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 );
            }
        }

Là c'est pas bien compliqué : on cherche s'il y a un env_fog dans la map, grâce à la fonction UTIL_FindEntityByClassname. S'il y en a un, on envoie ses caractéristiques, soit pour les trois premiers sa couleur en RGB (BYTE), et pour les deux suivants, les deux variables membres de la classe : StartDist et EndDist (SHORT). Je vous explique à quoi elles servent en fin de tut.

Voilà, maintenant que le message est envoyé, on passe au client.

Côté client

On va faire ça proprement, c'est à dire avec une classe dérivée de CHudBase. Ceux qui connaissent pas, c'est un bon moyen d'apprendre car c'est vraiment très utile. Il suffit de créer un nouveau fichier : fog.cpp, et de mettre ça dedans :

/***
*
*        Copyright (c) 1996-2001, Valve LLC. All rights reserved.
*       
*        this product contains software technology licensed from Id
*        Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc.
*        All Rights Reserved.
*
*  Use, distribution, and modification of this source code and/or resulting
*  object code is restricted to non-commercial enhancements to products from
*  Valve LLC.  All other use, distribution, or modification is prohibited
*  without written permission from Valve LLC.
*
****/
//
// fog.cpp
//
// implementation of CHudFog class
//

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <glgl.h>


#include "r_studioint.h"
extern engine_studio_api_t IEngineStudio;




#include "hud.h"
#include "cl_util.h"
#include "parsemsg.h"

#include <stdio.h>
#include <string.h>

DECLARE_MESSAGE(m_Fog, Fog)

int CHudFog::Init(void)
{
    HOOK_MESSAGE(Fog);
    gHUD.AddHudElem(this);
    return 1;
};


int CHudFog::VidInit(void)
{
    return 1;
};

int CHudFog:: MsgFunc_Fog(const char *pszName,  int iSize, void *pbuf )
{
    m_iFlags |= HUD_ACTIVE;


    BEGIN_READ( pbuf, iSize );

    r = READ_SHORT();
    g = READ_SHORT();
    b = READ_SHORT();
    s = READ_SHORT();
    e = READ_SHORT();

    return 1;
}


int CHudFog::Draw(float flTime)
{
    if( r == 0 && g == 0 && b == 0 )//si c'est noir on affiche pas car on ne verrait rien
        return 1;
    if ( IEngineStudio.IsHardware() != 1)
        return 1;

    int a = 128;

    float fDensity = 0.5, fStart = s, fEnd = e;

    float fogColor[4];

    fogColor[0] = ((GLfloat)r)/255.0f;
    fogColor[1] = ((GLfloat)g)/255.0f;
    fogColor[2] = ((GLfloat)b)/255.0f;
    fogColor[3] = ((GLfloat)a)/255.0f;

    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogfv(GL_FOG_COLOR, fogColor);
    glFogf(GL_FOG_DENSITY, fDensity);
    glHint(GL_FOG_HINT, GL_NICEST);
    glFogf( GL_FOG_START, fStart);
    glFogf( GL_FOG_END, fEnd );
    glEnable(GL_FOG);
       
    return 1;
}

Ach! Ça fait un bout de code ! Mais bon, c'est que du copier-coller. Essayez de comprendre le code, c'est le but d'un tut je le rappelle...

Alors oui, on parle de CHudFog, mais où l'a-t-on déclarée ? Bonne question. Alors c'est dans hud.h :

//Bicou
//-----------------------------------------------------
//
class CHudFog : public CHudBase
{
public:
    int Init( void );
    int VidInit( void );
    int Draw(float flTime);
    int MsgFunc_Fog(const char *pszName,  int iSize, void *pbuf );
       
private:
    int r, g, b, s, e;
};
//Bicou -fin

Les membres, ce sont les membres habituels des CHudBase, r g b c'est la couleur, s et e ce sont les deux variables du fog : StartDist et EndDist.

Mais il manque un truc : faut déclarer une instance de notre classe. C'est dans hud.h, ligne 650 et quelques :

CHudGeiger             m_Geiger;
CHudBattery            m_Battery;
//Bicou
CHudFog                m_Fog;
CHudTrain              m_Train;
CHudFlashlight         m_Flash;

Voilà. Dernier petit truc : les initialisations, c'est dans hud.cpp :

m_Train.Init();
m_Battery.Init();
m_Fog.Init();//Bicou
m_Flash.Init();
m_Message.Init();

et puis, plus bas :

m_Train.VidInit();
m_Battery.VidInit();
m_Fog.VidInit();
m_Flash.VidInit();
m_Message.VidInit();

Enfin ! C'est fini, votre fog est op. Maintenant on va passer aux réglages.

Mais nous avons oublié de définir les fonctions OpenGL que nous utilisons. Elles sont déclarées dans windows.h et gl/gl.h (Allez sur le site d'OpenGL si vous n'avez pas les lib OpenGL), mais il faut leur implémentation qui est dans opengl32.lib. Allez dans Project->Settings et pour le project client, allez dans l'onglet Link et dans Object/library Modules, mettez opengl32.lib à la fin de la liste. Compilez et c'est nickel !

Côté FGD

Le FGD ??? Qu'est-ce que c'est que cette connerie ??? Un tut sur ce sujet est disponible sur ce site.

Beh oui, faut ajouter notre entité dans le FGD. Ouvrez-le (notepad), et cherchez env_fade. Juste après, on met notre entité :

@PointClass base(Targetname) = env_fog : "Brouillard"
[
    startdist(string) : "startdist" : 50
    enddist(string) : "enddist" : 500
    rendercolor(color255) : "Color (R G B)" : "0 0 0"
]

Voilà, c'est fait. D'ailleurs je me permets de rajouter un petit élément au tut sur le FGD : un type de variable. Vous voyez que j'ai utilisé le type color255... Eh bien vous allez être contents : ça définit la couleur du fog, et pas besoin de se faire chier avec notre KeyValue, ça se met AUTOMATIQUEMENT dans pev->rendercolor. Elle est pas belle la vie !!!

Les variables, j'ai rien compris

D'abord, si vous avez rien compris : lisez le tut sur le FGD, ça devrait vous aider.

Sinon, les variables :

Ceux qui ont un effet pourri, moi j'utilise : StartDist à 50, EndDist à 500, rendercolor à 60 60 60, et ça rend très bien (j'ai une GeForce DDR, je sais pas si ça change quelque chose...).