Monday, 29 April 2019

Carga de superficies optimizada y estiramiento por software

Hasta ahora hemos estado aplicando Blit a nuestras imágenes en bruto. Ya que solo estábamos mostrando una sola imagen, esto no importa. Sin embargo, cuando estás haciendo un juego, este proceso ocasiona una pérdida de rendimiento innecesaria. Vamos a convertir las imágenes a un formato optimizado para acelerar la carga.

SDL2 también tiene una nueva función para las superficies llamada estiramiento por software, lo que te permite aplicar un blit a una imagen y modificar su tamaño. En este tutorial vamos a tomar una imagen de la mitad del tamaño de la pantalla, y la estiraremos al tamaño completo.

En nuestra función de carga de imágenes, le haremos unas cuantas modificaciones para que la superficie se convierta al momento de la carga. Al principio de la función tenemos la forma en la que hemos estado cargando superficies hasta ahora, pero también declaramos un puntero a la imagen final.

Si la imagen fue cargada correctamente en las líneas previas de código, optimizamos la superficie que fue cargada.

Verás, cuando cargas una imagen de mapa de bits, normalmente está cargada en un formato de 24 bits, ya que la mayoría de estas son de 24 bits. La mayoría, si no es que todas las pantallas modernas no son de 24 bits por default. Si aplicamos una imagen de 24 bits en otra imagen de 32 bits, SDL tendrá que convertirla cada vez que la imagen sea aplicada.

Por lo tanto, lo que vamos a hacer es que, al momento de cargar la imagen, la convertiremos al mismo formato que tenga la pantalla, así no ocurrirá una conversión en el momento de aplicación de esta. Esto se puede hacer facilmente con la función SDL_ConvertSurface. Todo lo que tenemos que hacer es pasarle la superficie que queremos convertir con el formato de la pantalla.

Es importante notar que la función regresará una copia de la superficie original en el formato nuevo. La original todavía se mantiene en memoria después de ejecutar esta llamada. Esto significa que tenemos que liberar la superficie original, o de otra forma tendremos 2 copias de la misma imagen en memoria.

Después de que la imagen se haya cargado y convertido, regresamos la imagen optimizada.

SDL2 ahora tiene una función dedicada para aplicar imágenes con un tamaño distinto: SDL_BlitScaled. Como hemos visto anteriormente, esta función toma una superficie fuente para aplicarla contra una superficie destino. También toma un SDL_Rect que define la posición y tamaño de la imagen a aplicar.

Asi que si queremos tomar una imagen que es más pequeña que la pantalla, y hacerla del tamaño de esta, tienes que hacer el ancho/alto sea el mismo que el de la pantalla.


¡Descarga el código fuente y los recursos de esta publicación aquí!

Sunday, 28 April 2019

Controlar la entrada del teclado

Presionar el botón X para salir de la ventana es solo uno de los eventos que SDL es capaz de manejar. Otro tipo de entrada bastante utilizado en juegos y es el teclado. En esta publicación vamos a presentar diferentes imágenes dependiendo de que tecla hayas presionado.

Justo debajo de la declaración de las constantes de la dimensión de la ventana declaramos una enumeración de las diferentes superficies que tenemos. Los enumeradores son una forma rápida de hacer constantes simbólicas en lugar crear constantes individuales.

Un mal hábito que los programadores principiantes tienen es usar números arbitrarios en lugar de constantes simbólicas. Por ejemplo, definir que "1" significa "menú principal", "2", "opciones", etcétera. Esto está bien para programas pequeños, pero cuando estamos lidiando con miles de líneas de código (que es muy común en videojuegos), tener una línea que utiliza constantes simbólicas resulta más cómodo.

Junto con los prototipos de función usuales, tenemos una nueva función loadSurface. Una regla en general, si estás copiando y pegando código, estás haciendo algo mal. En lugar de copiar y pegar el código de carga todas las veces, vamos a utilizar una función que se encargue de eso.

Lo que importa para este programa en específico es tener un arreglo de punteros a superficies de SDL llamado gKeyboardSurfaces, para contener todas las imágenes que vamos a utilizar. Dependiendo de que tecla presiones el usuario, usaremos gCurrentSurface (La imágen que se le aplicará dibujará en la pantalla) a una superficie.

Esta es la función loadSurface, que carga una imagen y reporta un error si algo sale mal. Es prácticamente lo mismo que antes, pero ahora con el manejo y reporte de errores en una función, lo que hace más fácil agregar y depurar la carga de imágenes.

Esta función no tiene el manejo de recursos dentro de la adquisición de estos. No se encarga de desalojar la memoria una vez que se haya asignado a la imagen. La función que solicito el acceso debe de encargarse de liberar los recursos una vez que haya terminado con la imagen, no al termino del ciclo de vida del archivo. Quizá en otro tiempo hablaremos de punteros inteligentes y RAII.

En esta función loadMedia vamos a cargar todas las imágenes que vamos a renderizar en la pantalla.

En la función principal antes de entrar al ciclo principal, vamos a asignar la imagen predeterminada a la superficie actual.

Aquí se encuentra nuestro ciclo de eventos. Como se vio en el tutorial anterior, Programación Orientada a Eventos, ahora manejaremos el evento SDL_KEYDOWN. Este evento ocurre cuando el usuario presiona una tecla con el teclado.

Dentro de un SDL_Event hay un SDL_Keyboard_event, el cual contiene información del evento de la tecla. Dentro de tal, hay un SDL_Keysym, el cual contiene información sobre que tecla fue presionada.

Como podrás ver, lo que este código hace es establecer las superficies en base a que tecla fue presionada. Puedes buscar en la documentación de SDL para ver cuales son los otros códigos de tecla para las demás teclas.

Después de que las teclas hayan sido manejadas y la superficie se haya establecido, le hacemos blit a la superficie para aplicarla en la pantalla.


¡Descarga el código fuente y los recursos de esta publicación aquí!

Thursday, 25 April 2019

Programación Orientada a Eventos

Además de poner imágenes en la pantalla, los juegos requieren que manejes la entrada del usuario. Puedes lograr esto con SDL usando el sistema de manejo de eventos.

En nuestro código, justo después de que SDL se haya inicializado y los medios estén cargados (como se mencionó en el tutorial anterior), declaremos nuestra bandera para el ciclo principal que se encarga de revisar si el usuario ha salido o no de la aplicación. Ya que apenas hemos comenzado el programa, es lógico que la incialicemos en falso.

También querremos declarar una unión de SDL_Event. Un evento en SDL puede ser algo como la acción de una tecla, el movimiento del mouse, accionar algún botón en un control, etc. En esta aplicación, solo buscaremos eventos de salida para finalizar la aplicación.

En tutoriales previos, hicimos que el programa esperara por unos cuantos segundos antes de cerrarse. Pero en esta aplicación el programa esperará hasta que el usuario salga antes de cerrarse.

Así que tendremos el ciclo de la aplicación mientras que el usuario no haya cerrado. Este ciclo que se mantiene en ejecución mientras la aplicación corre se conoce como el ciclo principal, conocido en algunos casos como el ciclo del juego, es el núcleo de cualquier juego.

En la cima de nuestro ciclo principal tenemos nuestro ciclo de eventos. Lo que lleva acabo es procesar la fila de eventos hasta que se encuentre vacía.

Cuando presionas una tecla, mueves el mouse o tocas la pantalla, agregas eventos a la fila de eventos.



La fila de eventos los almacena en el orden en que se van presentando, esperando a ser procesados. Cuando quieres averiguar que eventos ocurrieron para poder procesarlos, sondeas la fila para obtener el evento más reciente, llamando al método SDL_PollEvent. Lo que esta función hace es tomar el evento más reciente de la fila, y poner los datos del evento en el SDL_Event que pasamos a la función.

SDL_PollEvent va continuar removiendo eventos de la fila hasta que queda vacía. Cuando esto suceda, la función regresara 0. Por lo tanto, estas líneas de código continúan sondeando eventos de la fila hasta que esté vacía. Si un evento de la fila es un evento SDL_QUIT (cuando el usuario presiona la X de la ventana para cerrarla), ponemos la bandera de salida en verdadero para salir de la aplicación.

Luego de que hayamos terminado de procesar los eventos para nuestro frame, dibujamos en la pantalla y la actualizamos. Si la bandera de salida se establece en verdadero, la aplicación saldrá al final del ciclo. Si se mantiene en falso continuará hasta que el usuario cierre la ventana.


¡Descarga el código fuente y los recursos de esta publicación aquí!

Wednesday, 24 April 2019

Mostrar una imagen en la pantalla

Ahora que ya logramos abrir una ventana, ¡vamos a ponerle una imagen!.

Anteriormente, solíamos poner todo el código en la función main. Ya que se trataban de programas sencillos entonces si se valía, pero en la vida real (como al hacer videojuegos) quizá quieras tener tu código disponible en forma de modular. Esto significa que tendrás tu código dividido en pequeñas piezas, fáciles de depurar y reutilizar.

En este caso, significa que tendremos funciones para manejar la inicialización, carga de los medios y el cierre de la aplicación SDL. Vamos a declarar estas funciones cerca del tope de nuestro fichero main.cpp.

Esta función (que se localizará en el scope global en nuestro fichero) va ocasionar problemas en compiladores de C, debido a que este lenguaje como sabrán, no soporta sobrecarga de métodos. Esto no será problema si usamos un compilador de C++.

Aquí declaramos un par de variables globales, Normalmente querríamos evitar usar variables globales en programas grandes. La razón por la que las usamos aquí es porque buscamos tener el código fuente tan simple como sea posible, pero en proyectos grandes las variables globales hacen las cosas más complicadas. Ya que este programa solo es de un solo fichero no tendremos que preocuparnos mucho por esto.

Aquí nos topamos con un nuevo tipo de dato llamado SDL_Surface. Este es solamente una tipo de dato de imagen que contiene los pixeles de una imagen junto con los datos que necesita para renderizarla. Las SDL_Surface utilizan renderizado por software, por lo que funciona por medio del CPU. Es posible usar aceleración por hardware pero es un poco más difícil, por ahora vamos a ver la versión sencilla, ya veremos en nuevas entradas como hacerlo por hardware.

Ah, por cierto, recuerda siempre inicializar tus punteros. Los vamos a establecer en NULL inmediatamente de haberlos creado.

Como podrás ver, tomamos todo el código de inicialización de SDL y creación de la ventana y lo pusimos en su propia función. Un cambio que hicimos es que ahora hay una llamada a SDL_GetWindowSurface.

Queremos mostrar imágenes dentro de la ventana, por lo que tenemos que meter la imagen en la ventana. Para eso llamamos a SDL_GetWindowSurface para obtener un puntero a la superficie contenida por la ventana.

En la función loadMedia vamos a cargar nuestra imagen usando la función de SDL, SDL_LoadBMP. Esta función toma la ruta de un archivo bmp y regresa la superficie cargada. Si la función regresa NULL, esto significa que ocurrió un error, el cual vamos a mostrar en la consola usando SDL_GetError.

Un detalle muy importante a notar es que este snippet de código asume que tienes un archivo llamado sonyericsson.bmp en el directorio de trabajo. El directorio de trabajo es donde la aplicación piensa que está operando. Normalmente, es donde se encuentra el ejecutable, pero algunos programas como Visual Studio cambian el directorio a donde se encuentra el fichero vcxproj. En caso de que no se pueda cargar tu imagen, asegúrate de que se encuentre en el directorio correcto.

Una vez más, si tu programa se ejecuta correctamente pero no puede localizar la imagen, es probable que tengas un problema con relación al directorio de trabajo. Estos funcionan de manera distinta de SO a SO, de IDE a IDE etc. Si no encuentras una solución a un problema, te recomiendo mover el fichero bmp hasta que encuentres donde debe de ir.

En nuestra función de limpieza, destruimos la ventana y cerramos SDL como lo hemos hecho anteriormente, pero también nos ocupamos de la superficie que habíamos cargado. Esto lo hacemos al liberarla de la superficie. No te preocupes por la superficie de la ventana, la función SDL_DestroyWindow se encarga de destruirla también.

Asegúrate de hacerte el hábito de tener tus punteros apuntando a NULL cuando no están apuntando a nada.

En nuestra función main vamos a inicializar SDL y cargar la imagen, si tuvimos éxito, vamos a aplicar un blit desde la superficie cargada hacia la superficie de la pantalla, usando la función de SDL, SDL_BlitSurface.

Lo que el proceso de blitting hace es tomar una superficie de origen y estamparle una copia en la superficie destino. El primer argumento de esta función es la imagen (superficie) de origen, el tercer argumento es la imagen de destino. Por ahora no te preocupes por el segundo y cuarto argumento.

Si solo tuviéramos este comando de dibujo, no veríamos nada en la pantalla. Todavía nos falta un paso.

Después de dibujar todo en la pantalla que queremos mostrar por este frame tenemos que actualizar la pantalla usando la función SDL_UpdateWindowSurface. Cuando dibujas algo en la pantalla, no estás dibujando directamente en la pantalla que ves, por defecto, la mayoría de los renderizadores tienen un buffer doble, denominados el buffer frontal y el buffer trasero (o anterior y posterior).

Cuando ejecutas llamadas como SDL_BlitSurface, renderizas al buffer posterior. Lo que ves en la pantalla es el buffer anterior. La razón por la que funciona de esta manera es porque la mayoría de los frames requiere el dibujado de múltiples elementos en la pantalla. Si solo tuviéramos un solo buffer, seríamos capaces de ver como el frame se va llenando de dibujos, es decir, veríamos el frame sin terminar. Así que lo que buscamos es terminar de dibujar en el buffer posterior primero, y una vez que hayamos terminado, cambiar los buffer para que el usuario pueda ver el frame terminado.

Esto también significa que no llamas a la función SDL_UpdateWindowSurface después de cada blit, sino que una vez que todos los blits para el frame actual estén listos.



¡Descarga el código fuente y los recursos de esta publicación aquí!

Abrir una Ventana de SDL2

En este pequeño tutorial, daremos el primer paso: abrir una ventana.

Ahora que ya hemos configurado SDL2, es hora de crear una aplicación sencilla de SDL que dibuje un cuadrado en la pantalla.

El código estará dividido en distintas secciones, al final podrás descargar una copia del código completo. Iremos revisando estas piezas de código (snippets) poco a poco para explicar que hace cada línea.

Hasta arriba se encuentran los archivos fuente donde incluimos SDL, ya que vamos a necesitar las funciones y tipos de dato de SDL para ejecutar cualquier tipo de código SDL.

Vamos a utilizar también la salida/entrada estándar de C para imprimir errores a la consola. La función printf es un poco más segura en términos de hilo que usando iostream con cout, es un detalle menor, sin embargo luego encapsularemos la salida de errores y nos preocuparemos mucho por esto, usa lo que te parezca más conveniente.

Después de incluir las cabeceras, declaramos las constantes de ancho y alto de la ventana a la cual vamos a renderizar.

Aquí tenemos la parte superior de nuestra función main. Es importante que la lista de argumentos esté dada como se muestra, primero un int y luego un arreglo de caracteres, de no ser así, tendremos un error indicando una falla de referencia a main. SDL requiere que declaremos nuestra función main para ser compatible con múltiples plataformas.

Posteriormente declaramos nuestro puntero a la ventana de SDL que vamos a crear en un momento. Luego de eso tenemos nuestra superficie de SDL; una superficie es solamente una imagen 2D. Podemos cargar una imagen 2D desde un archivo o puede ser la imagen que se encuentre dentro de la ventana. En este caso, será la imagen que veremos de la ventana en la pantalla.

Después de declarar nuestra superficie y la ventana, inicializamos SDL. No puedes llamar ninguna función de SDL sin inicializar SDL primero. Ya que por ahora, lo único que necesitamos es utilizar el subsistema de video de SDL, solo estaremos pasando la bandera SDL_INIT_VIDEO.

Cuando haya un error, SDL_Init regresa un -1. Si esto llega a ocurrir, queremos imprimir el error que ocurrió en la consola, de otra manera la aplicación solo se lanzará por una fracción de un segundo y desaparecerá sin indicar que pasó.

Si nunca has usado printf antes, significa "print format" (imprimir con formato). Escribe la cadena de texto en el primer argumento con las variables en los siguientes argumentos. Cuando ocurra un error aquí "¡No se pudo inicializar SDL2! SDL_Error:" aparecerá en la consola, seguido por la cadena regresada por SDL_GetError. Este %s es el formato especial, significa que queremos mostrar una cadena de texto desde nuestra lista de variables. Ya que SDL_GetError es el único argumento, la cadena que regresa es lo que será agregado. SDL_GetError es una función que regresa el último error producido por una función de SDL.

SDL_GetError es una función muy útil, cada vez que tu aplicación vaya mal, necesitas saber el porque, y esta función te permitirá saber si alguna función de SDL presentó un error.

Si SDL se inicializó correctamente, vamos a crear una ventana usando la función SDL_CreateWindow. El primer argumento es el que define el título de la ventana.

El siguiente argumento define la posición X y Y donde será creada la ventana. Ya que por ahora esto no nos concierne, solo pondremos las constantes SDL_WINDOWPOS_UNDEFINED de SDL.

Los siguientes dos argumentos definen el ancho y el alto, y usamos nuestras constantes que definimos al principio de este post. Los últimos argumentos son las banderas de creación. SDL_WINDOW_SHOWN se asegura de que la ventana sea visible cuando es creada, este argumento es implícito si no está definido SDL_WINDOW_HIDDEN, el antónimo de esta bandera.

Si existe algún error, SDL_CreateWindow regresa un NULL. Si no existe una ventana, queremos imprimir el error en la consola.

Si la ventana fue creada con éxito, querremos obtener acceso a la superficie de esta para que podamos dibujar en ella. Para esto usaremos la función SDL_GetWindowSurface.

Vamos a mantener simple este tutorial, por lo que solo llenaremos la superficie de la ventana con el color más feo que podamos, usando la función SDL_FillRect. No te preocupes mucho acerca de esta función por ahora, lo que importa es que se muestre la ventana.

Algo muy importante a saber acerca de la renderización, es que solo porque hayas dibujado algo en la pantalla no significa que vayas a verlo, después de que hayas terminado de dibujar, se debe actualizar la ventana para que se muestre aquello que dibujaste. Para esto haremos una llamada a SDL_UpdateWindowSurface.

Si todo lo que hacemos es crear la ventana, llenarla y actualizarla, lo que veríamos sería solo un parpadeo de color por una fracción de segundo y eso sería todo. Para evitar que esto pase, vamos a llamar a la función SDL_Delay. Esta función esperará por una cantidad de milisegundos dados. Un milisegundo es 1/1000 de segundo. En nuestro código, esta función esperará por 2 segundos.

Un detalle importante es que, al estar en modo de espera, SDL no puede aceptar entrada del teclado o el mouse. Así que no te preocupes si al ejecutar este programa no responde, todavía no le damos código para tratar con el teclado o el mouse.

Después de que la ventana haya esperado por 2 segundos, la destruimos para liberar su memoria. Esto también se ocupará de la superficie que obtuvimos de esta. Después de que se haya liberado el espacio, cerramos SDL y regresamos un 0 para indicar una terminación adecuada del programa.



¡Descarga el código fuente y los recursos de esta publicación aquí!

Tuesday, 23 April 2019

Usar un Makefile para facilitar la compilación de SDL2

El programa make que forma parte de MSYS nos permite escribir scripts de construcción que automatizarán el proceso de compilación. El siguiente es un ejemplo simplificado:

1
2
3
4
5
6
7
8
9
# FILES especifica que archivos se compilarán como parte del proyecto
FILES = main.cpp

# APP especifica el nombre del ejecutable
APP = main

# Este es el target que compilará nuestro ejecutable
all: $(FILES) 
 g++ $(FILES) -w -Wl,-subsystem,windows -lmingw32 -lSDL2main -lSDL2 -o $(APP)

Arriba del archivo se declara e inicializa el macro "FILES", el cual especifica que archivos serán compilados. Después, se inicializa el macro "APP" que especifica el nombre del ejecutable.

Después de inicializar estos 2 macros, se presenta un target all, el cual compila el programa. Continuado por las dependencias, que están definidas como el macro de la lista de archivos, ya que obviamente vamos a requerir el código fuente para poder compilar el programa.

Después de especificar el target y sus dependencia, el comando para crear el target se encuentra en la siguiente línea, luego de un carácter de tabulación. Este carácter es necesario, de no estar, make lo ignorará.

Como es de esperarse, el comando para compilar el programa es muy parecido a lo que usaríamos directamente en la línea de comandos. Una diferencia en particular es que ahora tenemos macros que usaremos en el comando, para hacer tareas como agregar archivos nuevos al proyecto mucho más fácil. Ya que solo tiene que cambiarse la definición del macro en lugar de cambiar el comando completo.

En los siguientes posts vamos a ir agregando más librerías, por lo que el uso de Makefiles va ser imprescindible. Ahora veamos un Makefile un poco más completo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# FILES especifica que archivos se compilarán como parte del proyecto
FILES = main.cpp

# CC especifica el compilador que usaremos
CC = g++

# COMPILER_FLAGS especifica las banderas que se le aplican al compilador.
# -w elimina TODAS las advertencias
# -Wl,-subsystem,windows elimina la ventana de la consola
COMPILER_FLAGS = -w -Wl,-subsystem,windows

# LINKER_FLAGS establece las librerías a usar durante el proceso de link
LINKER_FLAGS = -lmingw32 -lSDL2main -lSDL2

# APP specifies el nombre del ejecutable
APP = main

# Este es el objetivo para compilar nuestro ejecutable
all : $(FILES)
 $(CC) $(FILES) $(COMPILER_FLAGS) $(LINKER_FLAGS) -o $(APP)

Para usarlo, creamos un archivo en el mismo directorio donde tengamos nuestro código fuente, copiamos el código anterior y lo guardamos como Makefile (con mayúscula M, sin extensión). Ahora solo queda lanzar su línea de comando con acceso a make.exe, y ejecutarlo para compilar nuestro proyecto de ejemplo.

Preparar SDL para la Línea de Comandos (MSYS/MinGW)

I.- Descargar SDL

Lo primero que debes hacer es descargar las cabeceras y binarios de SDL. Estos se encuentran en la página oficial de SDL, específicamente esta página.

Development Libraries section in SDL Website
Dirígete hasta la parte inferior de la página, en la sección de "Development Libraries", y descarga la librería de desarrollo MinGW32.

Abre el archivo SDL2-devel-2.0.x-mingw.tar.gz, y posteriormente abre el archivo .tar que se encuentra dentro, con una carpeta; copia esta carpeta a donde desees, para este tutorial usaremos la raíz del sistema en C:\SDL.

II.- Configurar MinGW

Copia los contenidos de la subcarpeta lib de SDL2-2.0.x a la carpeta lib de MinGW, que debería estar localizada en C:\MinGW\lib.

Posteriormente, abre la carpeta include en el archivo y extrae la carpeta llamada SDL2 en tu carpeta include de MinGW, copiando todos los archivos de cabecera.

Ahora, extrae el fichero SDL2.dll de la carpeta bin al mismo directorio donde compilarás tus binarios (.exe), este archivo es redistribuible, ¡por lo que podrás (tendrás que) proporcionar al compartir tu aplicación!.

De otra forma, puedes copiar SDL2.dll a la carpeta C:\Windows\System32 (32 bits) o C:\Windows\SysWOW64 (64 bits), lo que permitirá que tu aplicación detecte SDL2.dll incluso cuando no se encuentra en la misma carpeta que tu .exe.

Un problema con este método es que, al tener múltiples aplicaciones SDL que utilizan diferentes versiones de la librería, tendrás conflictos de versión. Por ejemplo si tienes SDL 2.0.8 en System32, mientras que tu aplicación usa SDL 2.0.9 te vas a topar con problemas. Generalmente, deberías tener la versión adecuada de SDL2.dll en el mismo directorio que tu ejecutable, pues te facilitará tenerlo listo al momento de compartir de aplicación.

III.- Probando el compilador

Vamos a crear una simple aplicación que requiere de SDL, para ello abrimos un nuevo archivo de código fuente en tu editor favorito, y pegamos el siguiente código:

Para probarlo, lo compilamos. Abre una ventana de comandos e introduce la siguiente línea:

g++ -o main.exe main.cpp -lmingw32 -lSDL2main -lSDL2

Asegúrate de que SDL2.dll está en el mismo directorio que el ejecutable. Si no hay ningún error, todo está listo. De otra forma, regresa y revisa que no te hayas saltado ningún paso; también asegúrate de que tu compilador está funcionando correctamente.

Como nota final, en el archivo que descargamos hay una carpeta llamada docs, te recomiendo que la extraigas y la dejes en un lugar disponible para futura referencia.

Por cierto, esa línea de comando se nos va hacer un poquito tediosa de escribir cada vez que queramos compilar nuestro proyecto, por lo que en la siguiente entrada veremos como crear un Makefile para facilitarnos el proceso un poquito.