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.

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:

  • 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:

 

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

loading...

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.

 

Nace el foro Gamedev Hispano

Hoy tengo el placer de anunciar la inauguración del foro Gamedev Hispano.

Se trata de un punto de encuentro para preguntar, debatir y descubrir sobre desarrollo de juegos en la lengua de Cervantes. Inicialmente enfocado al desarrollo de juegos HTML5 (Phaser y Babylon), el foro huye de ser “otro más” de Unity/Unreal y se centra en un desarrollo de juegos quizá para algunos más artesanal.

screenshot-gamedevhispano-com-2016-12-05-15-00-18

Aunque Phaser y Babylon hayan sido los frameworks elegidos para el foro, nadie está excluido. Existen subforos de SDL, Ogre3D, PyGame, libGDX y Godot y foros de temática general como ¡Enseña tu juego! y Multimedia.

Os animo a todos a registraros en el foro y a crear un hilo. Que la gente participe y colabore para ayudarse mutuamente.

Gamedev Hispano

The Everything Building

The Everything Building es un juego que fue presentado al Ludum Dare 34. Ludum Dare es una game jam, la más popular quizá. Se trata de hacer un juego en 72 horas. Normalmente hay un tema para el juego y si te ciñes a él podrás optar a ganar en más categorías. El tema de esta vez fue “Two game controls” (dos controles). The Everything Building quedó segundo en la general aunque en la categoría de diversión ganó.

elevator

¡Probadlo! A mí me ha encantado, es un gran juego. Con solo dos controles (flecha arriba, flecha abajo) controlamos un ascensor. Tenemos que llevar a la gente de una planta a otra. Si hay demasiada gente espereando perdemos. Hasta ahí parece simple pero el tipo de personas que montan en el ascensor tiene efectos sobre el resto. Por ejemplo, existen las parejitas, con un corazón encima, que no van a revelar su planta hasta que no se encuentren con su pareja. Están los zombies que ahuyentan a la gente. Los perros ocupan poco espacio y entran 4 mientras que coches solo entran 1. Si llevas payasos con globos te será más fácil subir y más difícil bajar. Este comportamiento a la inversa si coges a un fortachón. Hay un personaje que puede cambiar el tipo de establecimiento de esa planta (que en definitiva es lo que genera los personajes especiales) y que nos puede servir para jugar táctico.

En el apartado técnico no hay ningún reproche, es ligero, tiene un diseño pixel art bellísimo, la música acompaña perfectamente y la jugabilidad está ajustada, ¡perderás en el mejor momento!

En definitiva, jugadlo, es de lo mejorcito que he visto en HTML5.

Mis felicitaciones a Olli Ethuaho, Kimmo Keskinen, Sakari Leppä, Valtteri Heinonen y Anastasia Diatlova. El código está en GitHub: https://github.com/Oletus/elevator

Mapas interactivos en HTML5 con SnapSVG

HTML5 ha llegado aquí y está para quedarse y puede usarse en prácticamente cualquier sitio. Además según la encuesta del blog HTML5 era uno de los temas en los que estabais más interesados. Hoy vamos a ver como se puede hacer un mapa interactivo fácilmente, compatible con PCs, tabletas y móviles y veremos como los podemos animar.

Haz click en la provincia de Valladolid múltiples veces

SnapSVG

Aquí entra en juego SnapSVG. Se trata de una librería para JavaScript financiada y acogida por Adobe. Es una mejora de la ya popular librería para tratar SVG conocida como RaphaëlJS (ambas librerías son del mismo autor). Sin embargo, SnapSVG aporta muchas mejoras respecto a RaphaëlJS. La fundamental es que SnapSVG permite cargar archivos SVG ya existentes.

snapsvg

Mapas en SVG

Actualmente es fácil encontrar mapas en SVG de cualquier territorio. Sin embargo para que sea fácil trabajar con ellos hay que procurar que estén preparados para interactuar con ellos. Es necesario que las etiquetas <path> posean un atributo id y sea fácilmente reconocible. En el caso del mapa de España que hay al principio, el mapa está muy bien organizado. Las provincias empiezan por pr, los enclaves por en y las islas por is. Así que Valladolid es pr_valladolid y Menorca es is_menorca. Encontrar mapas así ya puede ser más difícil pero no imposible.

Primeros pasos

En nuestro HTML creamos una etiqueta <svg> con un id, por ejemplo id=papel. Ya está. Ahora pasamos al JavaScript.

Primero necesitamos obtener un papel (Paper en la documentación), con la función Snap y un selector CSS obtenemos el papel que ya hemos creado.

var s = Snap("#papel");

Ahora ya podemos usar todo el poder de SnapSVG, pero si queremos trabajar con un SVG ya existente el procedimiento es un poco distinto.

var s = Snap("#papel"); // Obtenemos el papel
Snap.load("/mapa.svg",function(f){
	// Al cargar el mapa se nos devuelve un fragmento
    // los fragmentos contienen elementos de SVG
    // Como queremos añadir todos los elementos, los seleccionamos todos, como un único grupo
    // otra vez vemos los selectores CSS en acción
    var g = f.selectAll("*");
    // y ahora añadimos el grupo al papel
    s.append(g);
    
    // cuando querramos acceder a un elemento podemos usar un selector CSS
    var valladolid = s.select("#pr_valladolid");
});

Atributos

Podemos alterar los atributos de estilo de SVG. Para quién no los conozca, funcionan igual que las propiedades CSS pero se aplican de manera distinta. Con SnapSVG podemos cambiar esos atributos en tiempo de ejecución. Por ejemplo, el relleno (propiedad fill).

s.attr({
	fill: "red"
});
// Cambia el relleno a rojo, afecta a los elementos inferiores, en este caso como es el papel, afecta a todo el SVG.

Figuras simples

Podemos añadir figuras simples de manera muy sencilla

var rect = s.rect(0,0,100,20).attr({fill: "cyan"});
// Creamos un rectángulo de 100x20 en la posición (0,0) con relleno cyan.
// Luego lo podemos borrar
rect.remove();

Eventos y animaciones

Ahora viene la parte interesante, eventos y animaciones. SnapSVG soporta varios tipos de evento en cada elemento. Veamos el click simple aunque existe doble click, ratón por encima, táctil (aunque click funciona en pantallas táctiles).

var murcia = s.select("#pr_murcia");
murcia.click(function(){
	murcia.attr({
    	fill: "yellow"
    });
});

Podemos animar los elementos especificando las propiedades que cambian y su tiempo

murcia.animate({fill: "purple"},1000);

SnapSVG es muy potente y permite realizar muchas más operaciones, como elementos arrastrables, grupos, patrones, filtros y más. El objetivo, según Adobe, es ser el jQuery de los documentos SVG.

snapsvg-game

Escalar imagen automáticamente

viewBox

SVG es un formato vectorial así que podemos aumentar el tamaño sin tener que preocuparnos por los píxeles. Sin embargo si simplemente cambias el tamaño del elemento <svg> vía CSS verás que no funciona. Es necesario especificar un atributo viewBox y mantenerlo constante. Básicamente viewBox da las dimensiones reales del lienzo donde se dibuja el SVG. Si cambian width y height y viewBox también entonces la imagen no se escala, simplemente se amplía el área del lienzo. Algunos mapas en SVG no ofrecen viewBox. En ese caso espeficicamos como viewBox el tamaño original del fichero SVG. En el caso de querer ocupar toda la pantalla.

s.attr({ viewBox: "0 0 800 600",width: window.innerWidth, height: window.innerHeight});
window.addEventListener("resize",function(){
	s.attr({ viewBox: "0 0 800 600",width: window.innerWidth, height: window.innerHeight});
});

Cordova y Android

SnapSVG se puede usar en Apache Cordova. Sin embargo yo he tenido problemas de rendimiento con la configuración por defecto en Android. Para solventar este problema he tenido que:

Solo así conseguí un rendimiento decente dentro de Cordova.