Hello World usando SDL no Visual Studio

Hello World usando SDL no Visual Studio

A SDL é uma biblioteca para desenvolvimento de jogos cross-platform com código aberto que existe desde 1998. Diversos jogos foram publicados utilizando SDL, como por exemplo, Half-Life 2, Left 4 Dead 2, Dying Light, Factorio, FTL, entre muitos outros. Sua linguagem de programação principal é C e C++, porém existem alguns bindings que permitem programar em outras linguagens como C#, Go, Lua, Rust e Python. Por ser “apenas” uma biblioteca, não possui uma interface gráfica como a Unity ou Unreal. Neste post vou demonstrar o passo a passo para criar um projeto utilizando o Visual Studio no Windows.

Para começar faça o download do runtime binaries da sua plataforma. Se você está utilizando Windows 10, pode fazer download da versão 64-bit Windows. Também é possível utilizar o código fonte, mas o runtime binaries é o suficiente para iniciar um projeto. Para referência, a versão da SDL utilizada é 2.0.14.

Configuração no Visual Studio

Estou utilizando o Visual Studio 2019 Community Edition que pode ser baixado aqui. Apesar das imagens abaixo serem da versão 2019 a configuração é a mesma em outras versões do Visual Studio.

Após realizar o download da SDL faça a extração em algum local do seu computador sem espaços no nome dos diretórios. Eu gosto de colocar meus projetos em C:\Projects e qualquer biblioteca ou dependência (como os arquivos da SDL) em C:\Dev.

Vamos criar um projeto novo do tipo Console App. Este projeto vai ter apenas um arquivo cpp, onde vamos adicionar o código do Hello World. O nome deste arquivo não importa, mas gosto de renomeá-lo para main.cpp.

Com o projeto criado basta clicar com o botão direito no Project (dentro da Solution) e selecionar a última opção chamada Properties. A seguir iremos adicionar o caminho para as pastas include e lib\x64 em VC++ Directories, e as libs SDL2.lib e SDL2main.lib em Linker/Input, conforme as imagens abaixo. Lembre-se de selecionar All Platforms antes de iniciar a configuração.

Por fim, precisamos copiar o arquivo SDL2.dll que está em lib\x64 para dentro do projeto, junto com o nosso arquivo cpp. Esta é a última etapa para configurar o Visual Studio e compilar o projeto com a SDL.

Como realizamos toda a configuração utilizando a lib\x64 lembre de selecionar x64 na Visual Studio antes de compilar e executar o projeto.

Hello World

Um projeto com SDL segue uma sequência bem definida de métodos, os quais devem ser executados para que tudo funcione corretamente. O código abaixo é a base que iremos utilizar, contendo a importação do SDL, o tamanho da tela e da imagem que vamos exibir e o método principal da aplicação. Este código não faz nada, mas pode ser executado para testar se a SDL está configurada corretamente no projeto do Visual Studio.

#include <SDL.h>

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480

#define IMAGE_WIDTH 256
#define IMAGE_HEIGHT 256

int main(int argc, char* args[])
{
    return 0;
}

Os trechos de código a seguir estão dentro do método main(). Em primeiro lugamos vamos inicializar a SDL e verificar se foi inicializada com sucesso. Caso algum erro seja retornado, iremos exibir a mensagem de erro no console utilizando SDL_Log() e finalizar a aplicação. Mais detalhes do método de inicialização aqui.

if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
    SDL_Log("SDL_Init Error: %s\n", SDL_GetError());
    return -1;
}

A seguir criaremos nossa Window onde o jogo será exibido. No código abaixo, a janela será criada no centro da tela com o tamanho definido anteriormente, e o título Hello World SDL2. Você pode ver aqui todos os parâmetro em detalhes.

SDL_Window* window = SDL_CreateWindow("Hello World SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
if (window == nullptr)
{
    SDL_Log("SDL_CreateWindow Error: %s\n", SDL_GetError());
    return -1;
}

O último item necessário para terminarmos a inicialização da SDL é o Renderer, responsável por “desenhar” na tela criada anteriormente. Os detalhes da criação do renderer podem ser vistos aqui.

SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == nullptr)
{
    SDL_Log("SDL_CreateRenderer Error: %s\n", SDL_GetError());
    return -1;
}

Com a SDL inicializada e a window e o renderer criados, podemos desenhar uma imagem na tela. O código abaixo carrega uma imagem no formato BMP e a exibe no centro da tela. Primeiro criamos uma surface para então criar a texture. Com a textura criada, podemos remover a surface da memória (a texture será removida só no final da aplicação). O SDL_Rect possui as coordenadas onde a imagem será desenhada e seu tamanho, considerando que o ponto de origem da imagem é no seu centro. Por fim, desenhamos a texture na tela utilizando o SDL_Render.

SDL_Surface* image = SDL_LoadBMP("image.bmp");
if (image == nullptr)
{
    SDL_Log("SDL_LoadBMP Error: %s\n", SDL_GetError());
    return -1;
}

SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, image);
if (texture == nullptr)
{
    SDL_Log("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
    return -1;
}

SDL_FreeSurface(image);

SDL_Rect rect;
rect.x = (int)(WINDOW_WIDTH * 0.5f - IMAGE_WIDTH * 0.5f);
rect.y = (int)(WINDOW_HEIGHT * 0.5f - IMAGE_HEIGHT * 0.5f);
rect.w = IMAGE_WIDTH;
rect.h = IMAGE_HEIGHT;

SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, &rect);
SDL_RenderPresent(renderer);

Todo esse código acontece antes do main loop, ou seja, será executado apenas uma vez. A imagem do exemplo será desenhada apenas uma vez na tela e, como no loop abaixo não tem outra chamada para o SDL_Render, ela continuará desenhada até o fim. Este loop é usado apenas para ler o input do jogador e sair do loop se a janela for fechada.

while (true) {
    SDL_Event e;
    if (SDL_PollEvent(&e)) {
        if (e.type == SDL_QUIT) {
            break;
        }
    }
}

Quando o jogador fechar a tela e o jogo sair do loop vamos finalizar a SDL. Para isso tiramos tudo que foi alocado da memória, como texture, renderer e window. E por fim finalizamos a SDL.

SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

Assim temos nosso Hello World com uma imagem desenhada na tela. A class completa pode ser vista neste gist. Se tiver alguma dúvida sobre os métodos e objetos da SDL acesse a documentação da biblioteca, a qual contem bastante informação e exemplos práticos.

Cuidados

Pelo tamanho do post é possível ver que desenhar uma simples imagem na tela exige uma boa quantidade de código. Enquanto na Unity, isso pode ser feito praticamente arrastando uma imagem para dentro da engine, com a SDL tudo é mais “manual”. Isso pode dar mais trabalho, mas também da mais controle ao programador. Por isso, é preciso prestar atenção para não fazer algo errado sem perceber.

Quando estava criando este exemplo para o post, percebi que o simples fato de desenhar uma imagem na tela estava utilizando 97% da minha GPU (o que é um absurdo). Notei que meu erro foi executar os métodos SDL_RenderClear(), SDL_RenderCopy() e SDL_RenderPresent() a cada loop (é um exagero para uma imagem estática).

Movendo as chamadas destes métodos para fora do loop, a utilização da GPU caiu para 1%. Existem algumas técnicas para controlar melhor o main loop e o frame rate, assim como desenhar apenas quando a imagem mudar de posição, entre outras possibilidades. O que me pegou de surpresa foi a própria documentação ter este exemplo, de uma imagem estática sendo desenhada todo frame.

Conclusão

Trabalhar com a SDL é um pouco lento no início até ter a base do jogo pronta, depois disso é a programação do jogo em si. É mais complicado do que utilizar a Unity e C#, por exemplo, com uma curva de aprendizado bem maior. Porém, é bem recompensador e importante aprender a programar (e programar jogos) em um “nível mais baixo” do que em uma engine que já te dá tudo pronto e você não vê muito bem o que acontece por trás do seu script.

O código completo está disponível neste link. Caso queira se aprofundar, recomendo olhar a documentação oficial. Este site também possui bons exemplos de programação de jogos com SDL.

comments powered by Disqus