Post

Fundamentos Para Criar um Jogo Cross-Platform

Fundamentos de Cross-Platform

Existem 3 maneiras de se criar um projeto cross-platform.

  1. Usando pre-processor #define com if/elses distinguindo o que o programa deve fazer no mesmo arquivo (o que não recomendamos).
  2. Ter um arquivo para cada platform-layer e um único arquivo comum, sendo que tratareremos nosso jogo virtualizando o sistema operacional.
  3. Ter um arquivo para cada platform-layer e um único arquivo comum, sendo que o jogo será tratado como um Service para o sistema operacional.

Virtualizando o sistema operacional para o jogo

Como mencionado no item 2, podemos criar um projeto cross-platform virtualizando o sistema operacional na “nossa cabeça”. Vamos ao exemplo:

1
2
3
4
// game.h
struct Window;
void game_main();
void game_shutdown();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// game.cpp

struct Window 
{
    // win32 stuff prop.
}

void game_main()
{
    Window window = platform_open_window();
    Sound sound = platform_open_sound();
}
void game_shutdown()
{
    platform_close_window(window);
    platform_close_sound(sound);
}

Definimos o comportamento do jogo a partir de um “game_main” onde ele irá virtualizar o que for necessário para a platform-layer funcionar.

1
2
3
4
5
6
7
8
9
10
11
12
13
// win32_game.cpp

Window platform_open_window()
{
    // code for open window class
}

int CALLBACK
WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmd_line, int cmd_show)
{
    game_main();
}

Jogo como serviço para o sistema operacional

O jogo só precisa fornecer ao sistema operacional a atualização do gráfico e som.

Cada sistema operacional possui uma complexidade de definições como abrir arquivos, plug/unplug gamepad, etc. Então vamos fazer com que o sistema operacional somente fornceça para o jogo:

  1. output: o que desenhar?
  2. output: o que tocar?
  3. input: aqui está o input do usuário
  4. timing

Com isso criamos um sistema bidirecional onde o jogo terá serviços para fornecer à platform-layer (game logic) e a platform-layer poderá fornecer serviços a jogo.

Movendo a lógica para a camada do jogo

Vamos mover a lógica para o arquivo comum e criar uma struct com propriedades que sejam comuns entre todas as plataformas. Isto é, para desenhar pixels no buffer precisamos apenas da largura, altura, pitch e o bloco de memória void *.

1
2
3
4
5
6
7
8
// game.h
struct Game_Back_Buffer 
{
    void *memory;
    int width;
    int height;
    int pitch;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// game.cpp
internal void
render_gradient(Game_Back_Buffer *buffer, int xo, int yo)
{
    u8 *row = (u8 *) buffer->memory; 
    for(int y = 0; y < buffer->height; y++) {

        u32 *pixel = (u32 *) row; 
        for (int x = 0; x < buffer->width; x++) {
            u8 b = (u8) (x + xo);
            u8 g = (u8) (y + yo);
            
            *pixel = ((g << 8) | b);
            *pixel++;
        }

        row += buffer->pitch;
    }
}

Agora, nas platform-layers, basta passar o ponteiro dessa struct onde ela irá popular o que precisamos na platform-layer.

1
2
3
4
5
6
7
8
// win32_game.cpp
Game_Back_Buffer buffer = {};
buffer.memory = g_back_buffer.memory;
buffer.width  = g_back_buffer.width;
buffer.height = g_back_buffer.height;
buffer.pitch  = g_back_buffer.pitch;

game_update_and_render(&game_memory, new_input, &buffer); 

A função game_update_and_render irá receber a struct comum já com os valores criados na platform-layer.

Após modificá-la e realizar o update final, os pixels irão aparecer.

Esta postagem está licenciada sob CC BY 4.0 pelo autor.