DEV Community

Andrey_vdl
Andrey_vdl

Posted on

Básico Sobre A MiniLibX

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

  1. Começando
  2. Imagens
  3. Teclado
  4. Mouse
  5. XPM
  6. Problemas Com A Produção
  7. Vídeo

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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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.

Espero que isso renderize uma janela com tela preta

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);
}
Enter fullscreen mode Exit fullscreen mode

Com isso você deve ter uma tela preta com um retângulo branco no canto superior esquerdo.

Tipo esse

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Tá começando a parecer um pong

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);
}
Enter fullscreen mode Exit fullscreen mode

0x0

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Ué não mudou?

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",
"+----",
"++---",
"+++--",
"++++-",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"+++++",
"++++-",
"+++--",
"++---",
"+----"
};
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Juro que era pra ser uma raquete

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)