Primeira janela no Windows
Nas próximas linhas irei mostrar como iniciar uma janela com um looping para iniciar a renderização de pixels na tela.
Vamos começar renderizando com a CPU para depois usar a GPU no futuro.
Os passos para chegar no resultado de uma janela para o jogo serão:
- Definir uma struct
WNDCLASS
com as definições - Definir um ponteiro (WindowProc) nessa Window que será nossa função de callback com os eventos de teclado e eventos do Windows para a janela
- Registrar a
WNDCLASS
para poder criar uma Window de fato com oRegisterClass
- Criar a janela através das instrução
CreateWindow
- Criar um main-loop para manter a janela ativa e dispachar os eventos que o Windows fornece para o procedure (GetMessage + WindowProc)
Criando a WindowClass
Algumas propriedades que vamos usar:
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagWNDCLASSA {
UINT style;
WNDPROC lpfnWndProc;
// int cbClsExtra;
// int cbWndExtra;
HINSTANCE hInstance;
// HICON hIcon;
// HCURSOR hCursor;
// HBRUSH hbrBackground;
// LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
style
: binary informationlpfnwndproc
: ponteiro para nossa função de callback que irá escutar eventos da janela (resize, create, etc)cbClsExtra
ecbWndExtra
: extras bytes alocados na sequencia da structhInstance
: handle instance que terá essa windowhIcon
: iconehCursor
: cursorhBrbackground
: Cor de fundolpszMenuName
: MenuslpszClassName
: O nome (String) da class
NOTA: Quando não tivermos o HWND e precisarmos dele, podemos buscar a hInstance com a seguinte função em qualquer lugar do nosso executável.
GetModuleHandle(0);
Window Class Flags
CS\_VREDRAW
: Redesenha toda a janela se o movimento ou ajuste do tamanho mudar na vertical.CS\_HREDRAW
: Redesenha toda a janela se o movimento ou ajuste do tamanho mudar na horizontal.CS\_OWNDC
: Aloca somente um único DeviceContext para cada janela.
Callback de eventos
Para capturar mensagens da janela, temos uma procedure para isso.
1
2
3
4
5
6
LRESULT Wndproc(
HWND window,
UINT message, // The message
WPARAM w_param, // Additional message information
LPARAM l_param // Additional message information
)
Exemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
LRESULT CALLBACK main_window_callback(HWND hwnd, UINT message,
WPARAM w_param, LPARAM l_param)
{
LRESULT result = 0;
switch(message) {
case WM_CREATE: {
// podemos pegar o valor do lParam passado ao criar uma window
OutputDebugStringA("WM_CREATE\n");
} break;
case WM_SIZE: {
OutputDebugStringA("WM_SIZE\n");
} break;
case WM_DESTROY: {
OutputDebugStringA("WM_DESTROY\n");
} break;
case WM_CLOSE: {
OutputDebugStringA("WM_CLOSE\n");
} break;
case WM_PAINT: {
// ...
} break;
case WM_ACTIVATEAPP: {
OutputDebugStringA("WM_ACTIVATEAPP\n");
} break;
default: {
result = DefWindowProc(hwnd, message, wParam, lParam);
} break;
}
return result;
}
LRESULT
possui 8 bytes
Criando a Janela
É preciso registrar a WNDCLASS para usá-la. RegisterClass(&window_class)
.
Caso a função retorne algo diferente de NULL, conseguimos criar a janela com o CreateWindow
.
It specifies the window class, window title, window style, and (optionally) the initial position and size of the window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
WNDCLASS windowClass = {};
windowClass.style = CS_OWNDC | CCS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = main_window_callback; // <- nosso callback
windowClass.hInstance = hInstance;
windowClass.lpszClassName = "MyWindowClass";
if (!RegisterClass(&windowClass)) {
// TODO: Log
return -1;
}
HWND window_handle = CreateWindowEx(
NULL, // DWORD dwExStyle valores extras
// exemplo Forces a top-level window onto the
// taskbar when the window is visible.
windowClass.lpszClassName, // LPCTSTR lpClassName
"MyGame", // LPCTSTR lpWindowName
WS_OVERLAPPEDWINDOW | WS_VISIBLE, // DWORD dwStyle
CW_USEDEFAULT, // int x
CW_USEDEFAULT, // int y
CW_USEDEFAULT, // int nWidth
CW_USEDEFAULT, // int nHeight
NULL, // HWND hWndParent
NULL, // HMENU hMenu
hInstance, // HINSTANCE hInstance
NULL // LPVOID lpParam
);
if (!window_handle) {
// TODO: log system
return -1;
}
// more code...
return 0;
O último parâmetro da CreateWindow
é um lpvoid onde podemos passar informações que chegarão no LParam
pela message WM_CREATE
na procedure de callback da window.
Main Loop
Precisamos extrair as mensagens que o Windows manda para nossa janela. Isto é, os eventos que o Windows dispara ou os inputs de teclado precisam ser extraídos de dentro do loop.
As mensagens ficam em uma fila e precisamos retirá-la com o GetMessage
.
NOTE:
GetMessage
irá travar a fila de mensagens. Futuramente vamos trocar paraPeekMessage
.
Depois de pegar cada uma das mensagens, vamos traduzi-las (Translates virtual-key messages into character messages) e dispachá-las para a procedure definida anteriormente no WNDCLASS com o DispatchMessage
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!window_handle) {
// TODO: log system
return -1;
}
MSG message; // windows vai colocar a mensagem aqui atraves do ponteiro
for(;;) {
// LPMSG lpMsg
// HWND hWnd (if NULL, busca mensagens de qualquer Window da thread atual)
// UINT wMsgFilterMin (filtro de mensagens disponiveis - range | zero = all messages)
// UINT wMsgFilterMax (filtro de mensagens disponiveis - range | zero = all messages)
BOOL result = GetMessage(&message, 0, 0, 0);
if (result > 0) {
TranslateMessage(&message);
DispatchMessage(&message); // Dispatches a message to a window procedure
} else {
break;
}
}
Colorindo a Janela
O Seguinte código é da window procedure onde podemos usar as instruções de BeginPaint
e EndPaint
para colorir a região da janela e usar a struct PAINTSTRUCT
para isso.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
case WM_PAINT: {
PAINTSTRUCT paint;
// busca as definicoes da hwnd para poder pintar nas dimensoes (x, y, w, h)
HDC deviceContext = BeginPaint(hwnd, &paint);
int x = paint.rcPaint.left;
int y = paint.rcPaint.top;
int height = paint.rcPaint.bottom - paint.rcPaint.top;
int width = paint.rcPaint.right - paint.rcPaint.left;
static DWORD operation = WHITENESS;
PatBlt(deviceContext, x, y, width, height, operation);
operation = operation == WHITENESS ? BLACKNESS : WHITENESS;
EndPaint(hwnd, &paint);
} break;
Notas Importantes
Static em variáveis locais
Quando definimos um static dentro de uma variável local, ele irá persistir como se fosse global. Contudo, sua visibilidade é somente aquele escopo de função.
Use somente para debug!!
1
static DWORD operation = WHITENESS;
Diferenças de Structs em C vs C++
Iniciando uma struct:
1
2
struct Foo foo; // C
Foo foo; // C++
Declarando uma struct:
1
2
3
4
5
6
7
8
// C
typedef struct {
// ...
} Name;
// C++
struct Name {
};
Inicializando uma struct:
Podemos iniciar uma struct com todos valores zerados com o bloco {}
.
1
WNDClass window_class = {};