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.