Tutorial de SDL – Parte 33 – Leitura e escrita de arquivos

Continuando nossa série de artigos traduzidos do site lazyfoo.net, agora veremos como ler e salvar arquivos no computador usando o SDL

Ser capaz de salvar e carregar dados é necessário para poder manter dados entre sessões de jogo. O manipulador de arquivos SDL_RWops permite que nós façamos entrada/saída em arquivo em várias plataformas para salvar dados.

//Data points
Sint32 gData[ TOTAL_DATA ];

Aqui declaramos um array de inteiros com sinal de tamanho 32 bits. Esse será o dado que estaremos carregando e salvando. No nosso exemplo, esse array terá o tamanho 10.

    //Open file for reading in binary
    SDL_RWops* file = SDL_RWFromFile( "33_file_reading_and_writing/nums.bin", "r+b" );

Na nossa função de carregamento de mídia, abrimos o arquivo para leitura usando SDL_RWFromFile. O primeiro argumento é o caminho para o arquivo e o segundo argumento define como iremos abri-lo. “r+b” significa que estamos abrindo ele para leitura em modo binário.

    //File does not exist
    if( file == NULL )
    {
        printf( "Warning: Unable to open file! SDL Error: %s\n", SDL_GetError() );
        
        //Create file for writing
        file = SDL_RWFromFile( "33_file_reading_and_writing/nums.bin", "w+b" );

Agora, se o arquivo não existe, isso não significa que ocorreu um erro. Pode significar que é a primeira vez que o programa foi  executado e o arquivo ainda não foi criado. Se o arquivo não existe, nós exibimos um aviso e criamos ele usando a opção “w+b” na função de abertura de arquivo. Isso irá abrir um novo arquivo para escrita em modo binário.

        if( file != NULL )
        {
            printf( "New file created!\n" );

            //Initialize data
            for( int i = 0; i < TOTAL_DATA; ++i )
            {
                gData[ i ] = 0;    
                SDL_RWwrite( file, &gData[ i ], sizeof(Sint32), 1 );
            }
            
            //Close file handler
            SDL_RWclose( file );
        }
        else
        {
            printf( "Error: Unable to create file! SDL Error: %s\n", SDL_GetError() );
            success = false;
        }
    }

Se um novo arquivo foi criado com sucesso, podemos começar a escrever os dados nele como SDL_RWwrite. O primeiro argumento é o arquivo que estamos escrevendo, o segundo argumento é o endereço do objeto que iremos salvar, o terceiro é o número de bytes por objeto que iremos salvar, e o último é o número de objetos que iremos salvar. Depois de terminarmos a escrita dos dados, fechamos o arquivo para escrita usando SDL_RWclose.

Se o arquivo nunca foi criado, reportamos um erro no terminal e ajustamos a flag de sucesso para false.

    //File exists
    else
    {
        //Load data
        printf( "Reading file...!\n" );
        for( int i = 0; i < TOTAL_DATA; ++i )
        {
            SDL_RWread( file, &gData[ i ], sizeof(Sint32), 1 );
        }

        //Close file handler
        SDL_RWclose( file );
    }

Agora se nosso arquivo foi carregado com sucesso na primeira tentativa, tudo que temos que fazer agora pe ler os dados usando SDL_RWread, que basicamente funciona como SDL_RWwrite mas de forma inversa.

    //Initialize data textures
    gDataTextures[ 0 ].loadFromRenderedText( std::to_string( (_Longlong)gData[ 0 ] ), highlightColor );
    for( int i = 1; i < TOTAL_DATA; ++i )
    {
        gDataTextures[ i ].loadFromRenderedText( std::to_string( (_Longlong)gData[ i ] ), textColor );
    }

Depois que o arquivo é carregado nós renderizamos a textura do texto que corresponde à cada um dos nossos números. Nossa função loadFromRenderedText aceita apenas strings então temos que converter inteiros para strings.

void close()
{
    //Open data for writing
    SDL_RWops* file = SDL_RWFromFile( "33_file_reading_and_writing/nums.bin", "w+b" );
    if( file != NULL )
    {
        //Save data
        for( int i = 0; i < TOTAL_DATA; ++i )
        {
            SDL_RWwrite( file, &gData[ i ], sizeof(Sint32), 1 );
        }

        //Close file handler
        SDL_RWclose( file );
    }
    else
    {
        printf( "Error: Unable to save file! %s\n", SDL_GetError() );
    }

Quando encerramos o programa, abrimos o arquivo novamente em modo de escrita e salvamos todos os dados.

            //Main loop flag
            bool quit = false;

            //Event handler
            SDL_Event e;

            //Text rendering color
            SDL_Color textColor = { 0, 0, 0, 0xFF };
            SDL_Color highlightColor = { 0xFF, 0, 0, 0xFF };

            //Current input point
            int currentData = 0;

Antes de entrarmos no loop principal declaramos currentData para manter um registro de quais inteiros estamos alterando. Também declaramos uma cor para o texto puro e uma cor de realce para o texto renderizado.

                    else if( e.type == SDL_KEYDOWN )
                    {
                        switch( e.key.keysym.sym )
                        {
                            //Previous data entry
                            case SDLK_UP:
                            //Rerender previous entry input point
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), textColor );
                            --currentData;
                            if( currentData < 0 )
                            {
                                currentData = TOTAL_DATA - 1;
                            }
                            
                            //Rerender current entry input point
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), highlightColor );
                            break;
                            
                            //Next data entry
                            case SDLK_DOWN:
                            //Rerender previous entry input point
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), textColor );
                            ++currentData;
                            if( currentData == TOTAL_DATA )
                            {
                                currentData = 0;
                            }
                            
                            //Rerender current entry input point
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), highlightColor );
                            break;

Quando pressionamos seta pra cima ou pra baixo queremos renderizar novamente os dados antigos na cor do texto puro, mover para o próximo ponto de dados (com alguma verificação de limites), e renderizar os novos dados na cor de realce.

                            //Decrement input point
                            case SDLK_LEFT:
                            --gData[ currentData ];
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), highlightColor );
                            break;
                            
                            //Increment input point
                            case SDLK_RIGHT:
                            ++gData[ currentData ];
                            gDataTextures[ currentData ].loadFromRenderedText( std::to_string( (_Longlong)gData[ currentData ] ), highlightColor );
                            break;
                        }
                    }

Quando pressionamos seta para esquerda ou direita nós diminuímos ou aumentamos os dados atuais e renderizamos novamente a textura associada a ele.

                //Clear screen
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );

                //Render text textures
                gPromptTextTexture.render( ( SCREEN_WIDTH - gPromptTextTexture.getWidth() ) / 2, 0 );
                for( int i = 0; i < TOTAL_DATA; ++i )
                {
                    gDataTextures[ i ].render( ( SCREEN_WIDTH - gDataTextures[ i ].getWidth() ) / 2, gPromptTextTexture.getHeight() + gDataTextures[ 0 ].getHeight() * i );
                }

                //Update screen
                SDL_RenderPresent( gRenderer );

No final do loop principal renderizamos todas as texturas na tela.

Baixe os arquivos de mídia e do código fonte do exemplo desse artigo aqui.