Fundamentos Para Criar um Jogo Cross-Platform
Fundamentos de Cross-Platform
Existem 3 maneiras de se criar um projeto cross-platform.
- Usando pre-processor
#define
com if/elses distinguindo o que o programa deve fazer no mesmo arquivo (o que não recomendamos). - Ter um arquivo para cada platform-layer e um único arquivo comum, sendo que tratareremos nosso jogo virtualizando o sistema operacional.
- 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:
- output: o que desenhar?
- output: o que tocar?
- input: aqui está o input do usuário
- 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.