Tutorial de Piston, programa juegos en Rust

Ya he hablado de Rust varias veces en este blog. La última vez fue en el tutorial de Iron, que os recomiendo ver si os interesa el tema del desarrollo web backend.

Hoy vamos a hablar de Piston. Piston es una de las librerías más antiguas del ecosistema Rust. Surgida cuando todavía no existía Cargo, esta librería está pensada para el desarrollo de juegos. No es la única que existe en Rust pero sí la más conocida. Piston es una librería que te enseñará Rust de la mejor forma. Y ahora quiero disculparme, porque Piston no es una librería, son un montón, pero eso lo veremos enseguida. En primer lugar creamos un proyecto nuevo con Cargo.

Ahora abrimos el archivo Cargo.toml, vamos a añadir las dependencias necesarias. Las dependencias en Piston son un poco complicadas, veamos:

  • Existen las dependencias core, implementan la API fundamental pero no pueden usarse por separado, son window, input y event_loop. Se usan a través de piston.
  • Los backends de window, existen actualmente 3 backends: glutin, glfw, sdl2. Se importan manualmente.
  • Graphics, una API 2D, no presente en core, pero al igual que las dependencias core necesita un backend.
  • Los backends de graphics son varios: opengl, gfx y glium.
  • Existe una dependencia que nos deja todo montado, piston_window. Esta trae por defecto el core de Piston, glutin, graphics y gfx.
  • Luego existen dependencias extra, como por ejemplo para cargar texturas, estas las podremos ir añadiendo según las necesite el proyecto.

Para simplificar añadimos piston_window únicamente:

 

 

Ahora abrimos el archivo main.rs. Añadimos la crate de piston_window y los módulos que vamos a usar.

 

Así mismo definimos un par de cosas para el resto del programa, la versión de OpenGL que usará Piston internamente y una estructura para guardar los movimientos de teclado.

 

En la función main podemos crear la ventana, especificando título y tamaño. Más opciones como V-Sync, pantalla completa y demás también están disponibles.

 

Ahora cargamos la tipografía Sinkin Sans, que vamos a usar para dibujar texto en pantalla. Como hay dos posibles localizaciones comprobamos esos dos lugares antes de salir del programa si no se consigue cargar la fuente.

 

Inicializamos la estructura de movimientos, generamos las dimensiones iniciales del rectángulo (que será un cuadrado en este caso), su color y la posición del ratón.

 

Ahora viene la parte importante, el bucle de eventos. El bucle va a funcionar infinitamente generando eventos por el camino (pueden ser eventos de inactividad también). Usamos la función draw_2d para dibujar en 2D. Hay dos maneras de dibujar un rectángulo, en primer lugar tenemos la forma abreviada y en segundo lugar una más completa que permite más opciones. Por último dibujamos el texto usando la fuente y realizando una transformación para que no quede el texto en la posición 0,0.

 

A continuación vamos a tratar cada evento de forma independiente, como todos los métodos devuelven Option, hemos de usar esta sintaxis con Some. En primer lugar tenemos un UpdateEvent, que básicamente nos informa del tiempo delta transcurrido. Recomiendo usar este evento para realizar los cambios en las geometrías, en este caso para mover el rectángulo.

Los siguientes dos eventos son opuestos, uno se activa cuando pulsamos una tecla y el otro cuando la soltamos. Comprobamos la tecla y modificamos la estructura movement en consecuencia.

Por último, si queremos comprobar clicks del ratón hacemos algo similar. He añadido código para que cambio el color del rectángulo si pulsamos sobre él.

A continuación un pequeño evento que guarda la última posición del ratón.

 

Y con esto ya tenemos hecho un ejemplo en Piston.

Si quieres tener un ejecutable para Windows sin que se muestre primero la consola debes compilar la versión que vas a distribuir con unos parámetros especiales. Si usas Rust con GCC usarás:

Si por el contrario usas Visual C++:

 

Piston todavía se encuentra en fuerte desarrollo, en la API estan documentados todos los métodos pero aun así muchas veces no se sabe como hacer ciertas cosas. Piston soporta además 3D, contando con una librería especializada en vóxels. Veremos como evoluciona esta librería.

Un nuevo lenguaje de programación para juegos

En la inocentada sobre Rust puse un vídeo de Jonathan Blow titulado Ideas about a new programming language for games. En el vídeo, Blow analiza los problemas que presenta C++ para el desarrollo de juegos y por qué según él ni Go ni D ni Rust consiguen mejorar la situación. El lenguaje de programación perfecto para juegos debería tener las siguientes características:

  • Poca fricción
  • Placer por programar
  • Rendimiento
  • Simplicidad
  • Diseñado para buenos programadores

Con poca fricción se refiere a que la tarea de programar no debe añadir mucha complejidad para solucionar problemas que tendríamos si programásemos de la forma más simple posible. Fricción es para él RAII en C++. Fricción es la gestión de errores en Rust. Fricción se entiende como código que no añade significativamente nada pero que es necesario para un correcto funcionamiento. Fricción es rellenar papeleo de Hacienda. Muchos defensores de estas posturas argumentan que en realidad esa productividad perdida se recupera con el tiempo al reducir el número de bugs que pueden ocurrir. Blow dice que según su experiencia en juegos AAA realmente no compensa. Tardas más tiempo solventado bugs potenciales que bugs reales. Su solución no es evitar al 100% este tipo de bugs (como hace Rust) sino habilitar unas herramientas potentes que ayuden a solucionar estos bugs si alguna vez suceden.

Esto se relaciona con el placer por programar. Un lenguaje que te invite a programar, a experimentar, donde te sientas a gusto. Muchos lenguajes han perdido esa esencia. Con el tiempo muchos lenguajes se han ido complicando de forma innecesaria y se han ido volviendo pesadillas. Ruby sería el caso contrario, un lenguaje que conserva ese placer. Pero Ruby no entra en juego por razones obvias de rendimiento.

Con rendimiento básicamente dice que cualquier lenguaje que disponga de GC (recolector de basura) no es válido. Incluso Go, que tiene un GC mucho mejor que Java o la plataforma .NET no lo considera correcto.

Con simplicidad se busca legibilidad y potencia. El lenguaje debe ser uniforme, con cohesión en todos sus elementos.

Y con diseñado para buenos programadores se refiere a que el lenguaje no debe suponer que el programador es idiota e intentar corregir sus errores. Debe permitir hacer virguerías si así lo desea el programador. Rust está bien pues permite tener código unsafe. Justo lo que se necesita para hacer virguerías. Pero hace falta más trabajo en este aspecto pues supone un cambio de mentalidad.

La idea detrás de RAII es incorrecta

Mucha gente opina que RAII es una de las mejores cosas que han pasado en la programación. Muchos lenguajes presuponen RAII. D por ejemplo considera que RAII es la manera correcta de programar. Resource Acquisition Is Initialization consiste en que cada vez que vamos a acceder a un recurso tenemos que codificarlo en una clase, inicializar el recurso en un constructor y liberar el recurso en un destructor. Añades operadores para permitir copia, … Este sistema presenta una elevada fricción. Y además no funciona bien, en el sentido de que todo se acaba sobrecomplicando. Alejándose de esa simplicidad que estamos buscando.

Uno de los principales problemas de este patrón de diseño es que no existe un recurso. Es una generalización errónea de varios conceptos. Un recurso puede ser memoria, otro recurso puede ser un archivo, una entrada de teclado, etc El problema es que estos recursos son demasiado diferentes como para ser tratados con un mismo patrón de forma óptima. Mientras RAII puede ser interesante hablando de archivos, es una muy mala opción si hablamos de memoria. Porque la memoria es el recurso más importante para un programador. Se podría simplificar diciendo que un programador lo único que hace es modificar la memoria constantemente.

Pero muchos de los usos de RAII tienen que ver con las excepciones. Y a Blow tampoco le gustan las excepciones. La gestión de errores en C es pésima pero las excepciones son muy complejas. Una de las cosas más complejas que implementan los lenguajes de programación que disponen de ellas. Y la implementación de C++ más todavía. Blow se lamenta de que haya gente que siga pensando que es una buena idea. Reduce la claridad del código, complica el flujo del programa. RAII en C++ ayuda a que en caso de que se de una excepción los recursos puedan ser liberados.

No solo lo dice él, sino que enlaza el siguiente vídeo: Systematic Error Handling in C++ por Andrei Alexandrescu.


Un mejor sistema que las excepciones

Go implementa múltiples valores de retorno (al contrario que la mayoría de lenguajes derivados de C donde solo de devuelve una cosa). Go lo soporta de forma nativa. Pero Matt Newport le responde como podría hacer eso en C++11 con std::tie.

Rust, como Go, soporta esto de forma nativa:

Aunque no es la manera en la que Rust maneja los errores. En su lugar posee Option y Result que en C++17 también van a ser implementados en std::optional y que es en realidad un concepto presente en Haskell.

Sintaxis exasperante

En la charla Blow sigue hablando y comenta que desde un punto de visto purista y correcto la sintaxis de punteros de C++11 es incorrecta. Que std::unique_ptr<Vector3[]> implica que quieres un Unique Ptr basado en Vector3 pero en realidad la idea correcta sería quiero un Vector3 con características de Unique Ptr. Lo mismo es aplicable para std::shared_ptr. Este tipo de punteros no deberían estar expresados de esta forma sino que deberían entrar en la sintaxis del lenguaje, por su utilidad práctica.

En Rust, el equivalente a std::unique_ptr sería Box que es el puntero más usado. El equivalente a std::shared_ptr sería Rc, no tan usado pero disponible.

Blow sigue hablando en este vídeo y en el siguiente de más errores que tiene C++, aunque de menor importancia. En todo caso, Blow sigue en el desarrollo de su compilador de Jai. C++ ha avanzado un montón y me ha llegado a sorprender que existiesen cosas como constexpr y los módulos de C++, una solución a los archivos de cabecera que son tan tediosos de escribir.

Si tenéis tiempo y sabéis inglés miráos el vídeo original. Creo que esta mucho mejor explicado que esto. Y también la respuesta de Matt Newport en la que dice que C++ SÍ es válido para todo lo que dice Blow.

Las 12 Uvas Game Jam

¿Quiéres hacer un juego en 15 días? ¿Estás listo para experimentar con ideas innovadoras y divertirte en el proceso?

las12uvasEl foro Gamedev Hispano presenta su primera game jam, un evento donde tendrás que desarrollar un juego de cero, teniéndolo que acabar antes de que termine el año.

Se trata de las 12 uvas game jam y cualquiera puede participar tanto de forma individual como por equipos. Esta competición tiene pocas reglas y es ideal si es tu primera game jam, aunque seguro que si has participado en otras también te gustará. El tema elegido es: Hasta la vista 2016.

Se puede programar el juego como quieras (Unity, Phaser, libGDX, Corona, LUA,…) siempre que sea capaz de funcionar al menos en Windows, Linux, Mozilla Firefox (HTML5) o Android. Se valorará creatividad y diversión, pero también se puntuará el uso que le dan los participantes al foro.

¿A qué esperas? Tienes toda la información en el foro.