como-programar-jogos,

Hello World usando SDL no Visual Studio

Bruno Cicanci Bruno Cicanci Seguir 21 de Março de 2021 · 9 min de leitura
Hello World usando SDL no Visual Studio
Compartilhe

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.

Criando um novo projeto Console App no Visual Studio

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.

Adicionando a pasta include do SDL no VC++ Directories

Adicionando a pasta lib/x64 do SDL no VC++ Directories

Adicionando dependências no Linker

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.

Copiar a DLL da SDL para o local dos arquivos

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

Mudar a plataforma ativa para x64 antes de compilar

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.

Mudar a plataforma ativa para x64 antes de compilar

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).

GPU com média de 97% de utilização

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.

GPU com média de 1% de utilização

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.

Gostou do post?
Como muitos programadores, aprecio um bom café. Se curtiu este conteúdo, que tal me presentear com um café?
Bruno Cicanci
Escrito por Bruno Cicanci Seguir
Bacharel em Ciência da Computação e pós-graduado em Produção e Programação de Jogos. Atuo profissionalmente com desenvolvimento de jogos desde 2010. Já trabalhei na Glu Mobile, Electronic Arts, 2Mundos, Aquiris, e atualmente na Ubisoft em Londres. Escrevo neste blog desde 2009.