Adrianistán

Tutorial de Piston, programa juegos en Rust

17/01/2017
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.
cargo new --bin ejemplo_piston
cd ejemplo_piston

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

Para simplificar añadimos piston_window únicamente:

 


[package]
name = "piston_example"
version = "0.1.0"
authors = ["Adrián Arroyo Calle"]

[dependencies]
piston_window = "0.59.0"


 

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


extern crate piston_window;

use piston_window::*;
use std::path::Path;


 

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.


const OPENGL: OpenGL = OpenGL::V3_1;

struct Movement{
up: bool,
down: bool,
left: bool,
right: bool
}


 

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.


fn main() {

let mut window: PistonWindow = WindowSettings::new("Piston - Adrianistan",[640,480])
.exit_on_esc(true)
.opengl(OPENGL)
.build()
.unwrap();


 

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.


let mut glyphs = Glyphs::new(Path::new("SinkinSans.ttf"),window.factory.clone()).unwrap_or_else(|_|{
let glyphs = Glyphs::new(Path::new("target/debug/SinkinSans.ttf"),window.factory.clone()).unwrap_or_else(|_|{
panic!("Failed to open the font file. Check that SinkinSans.tff is in the folder");
});
glyphs
});


 

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.


let mut mov = Movement{
up: false,
down: false,
left: false,
right: false
};

let mut dims = rectangle::square(50.0,50.0,100.0);
let mut rect_color = color::BLACK;

let mut mc: [f64; 2] = [0.0,0.0];


 

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.


while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([0.5, 0.5, 0.5, 1.0], g);
rectangle([1.0, 0.0, 0.0, 1.0], // color rojo, rgba
[0.0, 0.0, 100.0, 100.0], // dimensiones
c.transform, g); // transormacion y donde se va a dibujar

let rect = Rectangle::new(rect_color);
rect.draw(dims,&c.draw_state,c.transform,g);
text(color::BLACK,18,"¡Saludos desde Piston!",&mut glyphs,c.transform.trans(100.0,200.0),g); // aplicamos una transormacion, movemos las X 100 y las Y 200
});


 

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.


if let Some(upd_args) = e.update_args() {
let dt = upd_args.dt;

if mov.right {
dims[0] += dt*100.0;
}
if mov.left {
dims[0] -= dt*100.0;
}
if mov.up {
dims[1] -= dt*100.0;
}
if mov.down {
dims[1] += dt*100.0;
}
}


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.


if let Some(Button::Keyboard(key)) = e.press_args() {
if key == Key::W {
mov.up = true;
}
if key == Key::S {
mov.down = true;
}
if key == Key::A {
mov.left = true;
}
if key == Key::D {
mov.right = true;
}
};
if let Some(Button::Keyboard(key)) = e.release_args() {
if key == Key::W {
mov.up = false;
}
if key == Key::S {
mov.down = false;
}
if key == Key::A {
mov.left = false;
}
if key == Key::D {
mov.right = false;
}
};


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.


if let Some(Button::Mouse(mb)) = e.release_args() {
if mb == MouseButton::Left {
let x = mc[0];
let y = mc[1];
if x > dims[0] && x < dims[0] + dims[2] { if y > dims[1] && y < dims[1] + dims[3] {
rect_color = if rect_color == [1.0,0.0,0.0,0.7]{
[0.0,1.0,0.0,0.7]
} else if rect_color == [0.0,1.0,0.0,0.7] {
[0.0,0.0,1.0,0.7]
} else{
[1.0,0.0,0.0,0.7]
}
}
}

}
}


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


if let Some(mouse_cursor) = e.mouse_cursor_args() {
mc = mouse_cursor;
}
}
}


 

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:
cargo rustc --release -- -Clink-args="-Wl,--subsystem,windows"

Si por el contrario usas Visual C++:
cargo rustc --release -- -Clink-args="/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup"

 

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.
Tags: gfx opengl programacion gamedev glutin libreria linux rust tutorial sdl windows glfw programacion-de-videojuegos videojuegos piston 2d cargo