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.