Tutorial de SDL – Parte 38 – Motor de particulas

Continuando nossa série de artigos traduzidos do site lazyfoo, agora veremos como criar um motor de partículas.
Partículas são nada mais nada menos do que mini-animações. O que iremos fazer é pegar essas animações e coloca-las em torno de um ponto para criar uma trilha de partículas cintilantes coloridas.

//Particle count
const int TOTAL_PARTICLES = 20;
class Particle
{
    public:
        //Initialize position and animation
        Particle( int x, int y );
        //Shows the particle
        void render();
        //Checks if particle is dead
        bool isDead();
    private:
        //Offsets
        int mPosX, mPosY;
        //Current frame of animation
        int mFrame;
        //Type of particle
        LTexture *mTexture;
};

Aqui temos uma classe de partícula simples. Temos um construtor para configurar a posição, a função de renderização e uma função que informa se a partícula está morta. Em termos de atributos temos uma posição, um quadro de animação e uma textura a ser renderizada.

//The dot that will move around on the screen
class Dot
{
    public:
        //The dimensions of the dot
        static const int DOT_WIDTH = 20;
        static const int DOT_HEIGHT = 20;
        //Maximum axis velocity of the dot
        static const int DOT_VEL = 10;
        //Initializes the variables and allocates particles
        Dot();
        //Deallocates particles
        ~Dot();
        //Takes key presses and adjusts the dot's velocity
        void handleEvent( SDL_Event& e );
        //Moves the dot
        void move();
        //Shows the dot on the screen
        void render();
    private:
        //The particles
        Particle* particles[ TOTAL_PARTICLES ];
        //Shows the particles
        void renderParticles();
        //The X and Y offsets of the dot
        int mPosX, mPosY;
        //The velocity of the dot
        int mVelX, mVelY;
};

Aqui temos nosso ponto com um array de partículas e uma função para renderizar as partículas no ponto.

Particle::Particle( int x, int y )
{
    //Set offsets
    mPosX = x - 5 + ( rand() % 25 );
    mPosY = y - 5 + ( rand() % 25 );
    //Initialize animation
    mFrame = rand() % 5;
    //Set type
    switch( rand() % 3 )
    {
        case 0: mTexture = &gRedTexture; break;
        case 1: mTexture = &gGreenTexture; break;
        case 2: mTexture = &gBlueTexture; break;
    }
}

Para nosso construtor de partículas inicializamos a posição em torno de uma posição dada com alguma aleatoriedade. Então inicializamos o quadro de animação com uma aleatoriedade de forma que as partículas tenham uma vida variada. Finalmente escolhemos o tipo de textura que usaremos para a partícula também aleatoriamente.

void Particle::render()
{
    //Show image
    mTexture->render( mPosX, mPosY );
    //Show shimmer
    if( mFrame % 2 == 0 )
    {
        gShimmerTexture.render( mPosX, mPosY );
    }
    //Animate
    mFrame++;
}

Na função de renderização, renderizamos a textura selecionada no construtor e então todos os outros quadros renderizamos com uma textura cintilante semi-transparente sobre ela de forma a torna a partícula cintilantes. Em seguida, atualizamos o quadro da animação.

bool Particle::isDead()
{
    return mFrame > 10;
}

Assim que a partícula tiver sido renderizada por 10 quadros, marcamos ela como morta.

Dot::Dot()
{
    //Initialize the offsets
    mPosX = 0;
    mPosY = 0;
    //Initialize the velocity
    mVelX = 0;
    mVelY = 0;
    //Initialize particles
    for( int i = 0; i < TOTAL_PARTICLES; ++i )
    {
        particles[ i ] = new Particle( mPosX, mPosY );
    }
}
Dot::~Dot()
{
    //Delete particles
    for( int i = 0; i < TOTAL_PARTICLES; ++i )
    {
        delete particles[ i ];
    }
}

O construtor/destrutor agora tem que alocar/desalocar as partículas que renderizamos sobre o ponto.

void Dot::render()
{
    //Show the dot
    gDotTexture.render( mPosX, mPosY );
    //Show particles on top of dot
    renderParticles();
}
void Dot::renderParticles()
{
    //Go through particles
    for( int i = 0; i < TOTAL_PARTICLES; ++i ) { //Delete and replace dead particles if( particles[ i ]->isDead() )
        {
            delete particles[ i ];
            particles[ i ] = new Particle( mPosX, mPosY );
        }
    }
    //Show particles
    for( int i = 0; i < TOTAL_PARTICLES; ++i ) { particles[ i ]->render();
    }
}

A função de renderização de nosso ponto agora chama a função de renderização de nossa partícula. Essa função checa se existe algum partícula morta e substitui elas. Depois que as partículas mortas forem substituídas renderizamos todas partículas atuais na tela.

bool loadMedia()
{
    //Loading success flag
    bool success = true;
    //Load dot texture
    if( !gDotTexture.loadFromFile( "38_particle_engines/dot.bmp" ) )
    {
        printf( "Failed to load dot texture!\n" );
        success = false;
    }
    //Load red texture
    if( !gRedTexture.loadFromFile( "38_particle_engines/red.bmp" ) )
    {
        printf( "Failed to load red texture!\n" );
        success = false;
    }
    //Load green texture
    if( !gGreenTexture.loadFromFile( "38_particle_engines/green.bmp" ) )
    {
        printf( "Failed to load green texture!\n" );
        success = false;
    }
    //Load blue texture
    if( !gBlueTexture.loadFromFile( "38_particle_engines/blue.bmp" ) )
    {
        printf( "Failed to load blue texture!\n" );
        success = false;
    }
    //Load shimmer texture
    if( !gShimmerTexture.loadFromFile( "38_particle_engines/shimmer.bmp" ) )
    {
        printf( "Failed to load shimmer texture!\n" );
        success = false;
    }
    //Set texture transparency
    gRedTexture.setAlpha( 192 );
    gGreenTexture.setAlpha( 192 );
    gBlueTexture.setAlpha( 192 );
    gShimmerTexture.setAlpha( 192 );
    return success;
}

Para dar a nossas partículas um visual semi transparente ajustamos o fator alpha delas para 192.

            //Main loop flag
            bool quit = false;
            //Event handler
            SDL_Event e;
            //The dot that will be moving around on the screen
            Dot dot;
            //While application is running
            while( !quit )
            {
                //Handle events on queue
                while( SDL_PollEvent( &e ) != 0 )
                {
                    //User requests quit
                    if( e.type == SDL_QUIT )
                    {
                        quit = true;
                    }
                    //Handle input for the dot
                    dot.handleEvent( e );
                }
                //Move the dot
                dot.move();
                //Clear screen
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );
                //Render objects
                dot.render();
                //Update screen
                SDL_RenderPresent( gRenderer );
            }

Novamente, como nosso código está bem encapsulado, o código do loop principal praticamente não muda.

Como na maioria dos artigos anteriores, este é um exemplo bastante simplificado. Em programas maiores, haveriam partículas controlas por um emissor de partículas que tem sua própria classe, mas para tornamos as coisas mais simples temos uma função na classe Dot atuando como um emissor de partículas.

Baixe os arquivo de mídia e de código fonte desse artigo aqui.