Introdução
Olá, pessoal, hoje eu gostaria de fazer um pequeno tutorial sobre usar a MLX francesa.
Por mais que ela seja ruim, ela é bem básica, mas mesmo assim pega gente de surpresa.
PS. Esse texto é para dar um pequeno guia para quem tentou fazer algo e recebeu um segfault na cara.
Tabela de conteúdo
Começando
Bom todo projeto começa com uma preparação e a minha foi de fazer um simples Makefile:
TARGET=Pong.exec
LIB_MLX=minilibx/libmlx.a
FLAGS=-Wall -Wextra -Werror
MLX_FLAGS=-lXext -lX11 -lm -lz
SOURCES=main.c
all: ${TARGET}
${LIB_MLX}:
@make -C minilibx
${TARGET}: ${LIB_MLX} ${SOURCES}
cc ${FLAGS} -g3 -Iminilibx ${SOURCES} minilibx/libmlx.a -o ${TARGET} ${MLX_FLAGS}
clean:
rm -f ${TARGET}
.PHONY: clean
re: clean all
.PHONY: re
Depois disso seguimos com uma main bem simples e direta:
#include "minilibx/mlx.h"
#include <stdlib.h>
typedef struct ClassMlx
{
void* mlx;
void* win;
} ClassMlx;
int main(void)
{
ClassMlx* engine = calloc(1, sizeof(ClassMlx));
int ret = 0;
ret = initEngine(engine);
if (ret < 0) {
free(engine);
return (ret);
}
mlx_loop(engine->mlx);
destroyEngine(engine);
free(engine);
return (0);
}
int initEngine(ClassMlx* engine)
{
engine->mlx = mlx_init();
if (engine->mlx == NULL) {
return (-1);
}
engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
if (engine->win == NULL) {
mlx_destroy_display(engine->mlx);
free(engine->mlx);
return (-2);
}
return (0);
}
void destroyEngine(ClassMlx* engine)
{
mlx_destroy_window(engine->mlx, engine->win);
mlx_destroy_display(engine->mlx);
free(engine->mlx);
}
Não vou entrar em muitos detalhes sobre como as coisas funcionam debaixo dos panos, mas citei um pouco na minha documentação, vamos focar na produção. Nesse momento ao rodar o código temos uma tela preta que não fecha, por isso faça Ctrl+C no terminal.
Imagens
Bom vamos alterar um pouco o código para desenhar um quadrado branco na tela
#include "minilibx/mlx.h"
#include <stdlib.h>
typedef struct ClassMlx
{
void* mlx;
void* win;
void* img;
} ClassMlx;
int main(void)
{
ClassMlx* engine = calloc(1, sizeof(ClassMlx));
int ret = 0;
ret = initEngine(engine);
if (ret < 0) {
free(engine);
return (ret);
}
mlx_loop_hook(engine->mlx, renderGame, engine);
mlx_loop(engine->mlx);
destroyEngine(engine);
free(engine);
return (0);
}
int initEngine(ClassMlx* engine)
{
engine->mlx = mlx_init();
if (engine->mlx == NULL) {
return (-1);
}
engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
if (engine->win == NULL) {
mlx_destroy_display(engine->mlx);
free(engine->mlx);
return (-2);
}
engine->img = mlx_new_image(engine->mlx, 800, 600);
if (engine->img == NULL) {
mlx_destroy_window(engine->mlx, engine->win);
mlx_destroy_display(engine->mlx);
free(engine->mlx);
return (-3);
}
return (0);
}
int renderGame(void* param)
{
ClassMlx* engine = (ClassMlx*)param;
renderMap(engine);
mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
return (0);
}
void renderMap(ClassMlx* engine)
{
for (int y = 0; y < 300; y++) {
for (int x = 0; x < 400; x++) {
drawPixel(engine->img, x, y, 0xFFFFFF);
}
}
}
void drawPixel(void* img, int x, int y, int color)
{
int bpp = 0;
int size_line = 0;
int endian = 0;
char* data = mlx_get_data_addr(img, &bpp, &size_line, &endian);
char* pixel = NULL;
/*
* explicacao da formula:
* (y * size_line) = posicao vertical com base na largura da imagem.
* (bpp / 8) = converte os bytes da imagem em uma linha de pixels
* (x * (bpp / 8)) = escolhe o pixel da linha
*/
pixel = data + (y * size_line) + (x * (bpp / 8));
*(int*)pixel = color;
}
void destroyEngine(ClassMlx* engine)
{
mlx_clear_window(engine->mlx, engine->win);
mlx_destroy_image(engine->mlx, engine->img);
mlx_destroy_window(engine->mlx, engine->win);
mlx_destroy_display(engine->mlx);
free(engine->mlx);
}
Com isso você deve ter uma tela preta com um retângulo branco no canto superior esquerdo.
Vou só fazer uma pequena edição de uma função para nunca mais mexer nela (se você fizer a mesma alteração vai ter uma linha no meio da tela).
void renderMap(ClassMlx* engine)
{
for (int y = 0; y < 600; y++) {
drawPixel(engine->img, 400, y, 0xFFFFFF);
}
}
Teclado
Vamos mexer de novo com a main para poder controlar a raquete de pong (também aproveitei e adicionei um hook na função setEngine
que permite fechar a janela com o botão X):
#include "minilibx/mlx.h"
#include <X11/X.h>
#include <X11/keysym.h>
#include <stdlib.h>
typedef struct ClassPlayer
{
int x;
int y;
int moveStyle;
} ClassPlayer;
typedef struct ClassMlx
{
void* mlx;
void* win;
void* img;
ClassPlayer player;
} ClassMlx;
int main(void)
{
ClassMlx* engine = calloc(1, sizeof(ClassMlx));
int ret = 0;
ret = initEngine(engine);
if (ret < 0) {
free(engine);
return (ret);
}
setEngine(engine);
mlx_loop(engine->mlx);
destroyEngine(engine);
free(engine);
return (0);
}
int initEngine(ClassMlx* engine)
{
engine->player.x = 0;
engine->player.y = 300;
engine->player.moveStyle = 0;
engine->mlx = mlx_init();
if (engine->mlx == NULL) {
return (-1);
}
engine->win = mlx_new_window(engine->mlx, 800, 600, "PONG");
if (engine->win == NULL) {
mlx_destroy_display(engine->mlx);
free(engine->mlx);
return (-2);
}
engine->img = mlx_new_image(engine->mlx, 800, 600);
if (engine->img == NULL) {
mlx_destroy_window(engine->mlx, engine->win);
mlx_destroy_display(engine->mlx);
free(engine->mlx);
return (-3);
}
return (0);
}
void setEngine(ClassMlx* engine)
{
mlx_key_hook(engine->win, keyEvents, engine);
mlx_hook(engine->win, DestroyNotify, NoEventMask, mlx_loop_end, engine->mlx);
mlx_loop_hook(engine->mlx, renderGame, engine);
}
int keyEvents(int keycode, void* param)
{
ClassMlx* engine = (ClassMlx*)param;
if (keycode == XK_Up && engine->player.y > 10) {
engine->player.y -= 10;
}
if (keycode == XK_Down && engine->player.y < 590) {
engine->player.y += 10;
}
if (keycode == XK_space) {
engine->player.moveStyle = !engine->player.moveStyle;
}
if (keycode == XK_Escape) {
mlx_loop_end(engine->mlx);
return (0);
}
return (0);
}
int renderGame(void* param)
{
ClassMlx* engine = (ClassMlx*)param;
// esse for e para limpar a imagem antes de pintar ela de novo.
for (int y = 0; y < 600; y++) {
for (int x = 0; x < 800; x++) {
drawPixel(engine->img, x, y, 0);
}
}
renderMap(engine);
renderPlayer(engine);
mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
return (0);
}
void renderPlayer(ClassMlx* engine)
{
for (int pos_x = 0; pos_x < 5; pos_x++) {
for (int pos_y = engine->player.y - 10; pos_y < engine->player.y + 10; pos_y++) {
if (pos_y < 0 || pos_y >= 600) {
continue;
}
drawPixel(engine->img, pos_x, pos_y, 0xFF0000);
}
}
}
Só de extra vamos adicionar os placares:
int renderGame(void* param)
{
ClassMlx* engine = (ClassMlx*)param;
for (int y = 0; y < 600; y++) {
for (int x = 0; x < 800; x++) {
drawPixel(engine->img, x, y, 0);
}
}
renderMap(engine);
renderPlayer(engine);
mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
return (0);
}
Mouse
Vocês devem ter notado que configurei o espaço para alterar uma variável, mas ele não é utilizada em lugar nenhum, pois agora vamos utiliza-la:
int renderGame(void* param)
{
ClassMlx* engine = (ClassMlx*)param;
for (int y = 0; y < 600; y++) {
for (int x = 0; x < 800; x++) {
drawPixel(engine->img, x, y, 0);
}
}
movePlayerMouse(engine);
renderMap(engine);
renderPlayer(engine);
mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
return (0);
}
void movePlayerMouse(ClassMlx* engine)
{
int x = 0;
int y = 0;
if (engine->player.moveStyle == 0) {
return;
}
mlx_mouse_get_pos(engine->mlx, engine->win, &x, &y);
(void)x;
if (y > 10 && y < 590) {
engine->player.y = y;
}
}
Sei que para algumas pessoas parece certo utilizar a mlx_mouse_hook
para pegar o movimento do mouse, mas, na verdade do metódo que fiz agora, podemos pegar a posição do mouse antes de renderizar a raquete (sem contar que o mouse_hook só é ativado quando o mouse tem um clique ou scroll).
XPM
XPM é um formato de imagem bem desconhecido, mas que conquiste em um array de string do C.
/* XMP3 */
static char *place_holder[] = {
"5 30 2 1",
"+ c #FFFFFF",
"- c #0000FF",
"+----",
"++---",
"+++--",
"++++-",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"++++-",
"+++--",
"++---",
"+----"
};
Pra facilitar as coisas a MiniLibX só aceita XPM e ela carrega como uma imagem igual a mlx_new_image
:
int renderGame(void* param)
{
ClassMlx* engine = (ClassMlx*)param;
for (int y = 0; y < 600; y++) {
for (int x = 0; x < 800; x++) {
drawPixel(engine->img, x, y, 0);
}
}
movePlayerMouse(engine);
renderMap(engine);
// renderPlayer(engine);
mlx_put_image_to_window(engine->mlx, engine->win, engine->img, 0, 0);
mlx_string_put(engine->mlx, engine->win, 200, 42, 0xFF00, "0");
mlx_string_put(engine->mlx, engine->win, 600, 42, 0xFF00, "0");
renderPlayer2(engine);
return (0);
}
void renderPlayer2(ClassMlx* engine)
{
int width = 0;
int height = 0;
void* texture = mlx_xpm_file_to_image(engine->mlx, "./raquete.xpm", &width, &height);
if (engine->player.y > 0 && engine->player.y < 600) {
mlx_put_image_to_window(engine->mlx, engine->win, texture, engine->player.x, engine->player.y);
}
}
Preste atenção na ordem das put_image_to_window se não você vai ter apenas uma tela preta com uma linha no meio e dois números.
E pronto, acho que isso é realmente o básico que você precisa pra fazer algo com a MiniLibX, claro existem muitas outras funções, mas como citei você pode achar elas na minha documentação.
Problemas Com A Produção
Se você não falha em pelo menos 90% das vezes, seus objetivos não foram ambiciosos o suficiente. - KAY, Alan
Que ótima frase para começar essa seção (honestamente peguei ela quando comecei a escrever isso), isso, pois montar esse tutorial foi uma grande falha.
Para começar, a primeira falha foi do primeiro vídeo, que ficou parecendo uma reunião de PowerPoint (essa parte foi cortada do vídeo final).
A segunda foi eu ter que lidar com o Vim depois de 1 ano sem tocar nele, e ainda ter o GitHub Copilot me recomendando código errado que me fez pensar que eu não lembrava mais como codar com a MiniLibX.
Já a terceira falha foi o fato de tentar gravar áudio no calor com o ventilador desligado.
A quarta falha foi ver que eu leio um tele prompt mais rápido do que o texto sobe.
E a quinta falha foi por não conseguir gravar áudio porque o microfone do meu notebook não é sensível, o que me obrigaria a gritar.
Com isso, tudo o que sobrou foi um roteiro e um vídeo que se tornaram esse post.
Vídeo
Top comments (0)