1. Creamos un proyecto llamado "hello_ggez02" con "cargo new hello_ggez02"
2. Cambiamos el archivo toml para poner la dependencia a ggez:
1 2 3 4 5 6 7 8 | [package] name = "hello_ggez02" version = "0.1.0" authors = ["MiUsuario <micorreo@correo.com>"] edition = "2018" [dependencies] ggez = "0.5.0-rc.1" |
3. Compilamos el proyecto para que se descarguen y compilen las dependencias de ggez. Pero en realidad no cambiamos nada del código, es decir, seguía siendo el "Hola mundo" que genera "cargo new" de forma predeterminada.
Ahora modificaremos el código. La página de github guía la creación del archivo mediante modificaciones secuenciales. En vez de hacer eso, mostraré el código terminado.
Primero un poco de teoría sobre ggez:
- ggez require de un "struct" que tenga todo el estado del juego. Es decir, una representación de todo el juego en forma de datos. Es algo que representa todo el juego como datos en memoria.
- Vimos en los ejemplos anteriores como usar Result, en realidad ggez usa una variante llamada "GameResult" pero funciona de forma similar.
- Los juegos de vídeo son en cierto sentido un ciclo o bucle infinito donde se disparan eventos, actualizamos datos y dibujamos datos. Esta parte se llama el "game loop" o bucle de juego. Usando ggez este se encargará de configurar y administrar el ciclo y ggez sólo llamará nuestro código. En este caso ggez proporciona una interfaz ("trait") llamado EventHandler, esta nos guía para implementar lo que queramos que ggez llame durante el bucle del juego. Como requisito mínimo hay que implementar "update" y "draw", para actualizar los datos y dibujar el juego respectivamente. Estas dos funciones "update" y "draw" deben de regresar un "GameResult".
- ggez requiere que construyamos una variable que contiene las configuraciones. Podemos generar las opciones predeterminadas con "conf::Conf::new()".
- Usando las configuraciones anteriores, el nombre el programa (string) y el nombre del autor (string) podemos construir una variable de contexto y un bucle de juego. La función que usamos en ggez es "ContextBuilder::new("nombre_del_juego", "nombre_del_autor").conf(configuracion).build().unwrap()" y esta función regresa una tupla de referencias. Tenemos que hacer que la tupla encaje un patrón para descomponerla así que la asignamos a "(ref mut contexto, ref mut bucle_de_juego)" el "ref" es simplemente similar a usar "&" sólo que se usa "ref" cuando estamos buscando algo que encaje un patrón (lado izquierdo de la igualdad) en lugar de cuando lo usamos en el lado derecho de la igualdad. Es decir "let ref mut x = y" es lo mismo que "let x = &mut y".
- Finalmente le decimos a ggez que corra nuestro contexto, bucle de juego y estado de juego (struct). Esto lo hacemos usando "event::run" y le pasamos nuestro contexto, bucle_de_juego, y estado_del_juego.
- Agregar "use ggez::*;" al inicio del archivo. Esto es para que Rust cargue las variables y interfaces que vienen en ggez.
- Agregar un struct que contenga el estado del juego llamado "EstadoDelJuego". Es algo que representa todo el juego como datos en memoria. En este caso sólo tendrá una variable donde guardaremos una duración "dt" usando "std::time::Duration".
- Agregaremos una implementación de ggez::event:EventHandler al struct de EstadoDelJuego. EventHandler es una interfaz ("trait") que nos proporciona ggez. Dentro de esta interfaz nos pide implementar mínimamente los métodos "update" y "draw".
- El método "update" sirve para actualizar el EstadoDelJuego y toma como entrada el estado del juego ("self") y una variable de contexto que creamos con ggez. Dentro de la función "update" calculamos el "dt" usando "timer::delta(contexto)" que nos proporciona ggez. Esta función debe regresar un GameResult que es parecido a un Result, en este caso regresamos "Ok(())", que es Ok con la unidad (tupla vacía) adentro.
- El método "draw"sirve para dibujar en la pantalla el EstadoDelJuego y también toma como entrada el estado del juego ("self") y una variable de contexto que creamos con ggez. Dentro de esta función imprimimos nuestro valor de "dt" en nanosegundos. Esta función debe regresar un GameResult que es parecido a un Result, en este caso regresamos "Ok(())", que es Ok con la unidad (tupla vacía) adentro.
- En el método main hacemos las siguientes operaciones en orden:
- Inicializamos el estado del juego como una variable mutable y ponemos 0.0 en el valor de dt.
- Usamos "conf::Conf::new()" para crear una configuración predeterminada.
- Usamos "ContextBuilder::new" para crear la variable de contexto y la variable de bucle de juego a partir de nuestro nombre de autor (string), nuestro nombre del juego (string) y la variable de configuración. Llamamos el método ".build()" y luego ".unwrap()".
- Usamos "event::run" para decirle a ggez que empiece el bucle del juego con nuestra variable de contexto, nuestra variable de bucle de juego y nuestro estado del juego. De forma similar llamamos el método ".unwrap()".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | use ggez::*; struct EstadoDelJuego { dt: std::time::Duration } impl ggez::event::EventHandler for EstadoDelJuego { fn update(&mut self, contexto: &mut Context) -> GameResult<()> { self.dt = timer::delta(contexto); Ok(()) } fn draw(&mut self, _contexto: &mut Context) -> GameResult<()> { println!("Hola ggez! dt = {}ns", self.dt.subsec_nanos()); Ok(()) } } fn main() { let estado_del_juego = &mut EstadoDelJuego { dt: std::time::Duration::new(0,0) }; let configuracion = conf::Conf::new(); let (ref mut contexto, ref mut bucle_de_juego) = ContextBuilder::new( "hello_ggez", "autor") .conf(configuracion).build().unwrap(); event::run(contexto, bucle_de_juego, estado_del_juego).unwrap(); } |
Notar como en el código anterior estamos usando ".unwrap()" para desenvolver todas las salidas. En este caso no hay problema porque el ejemplo es muy fácil así que no esperamos que el programa entre en pánico por un Err en lugar de un Ok.
Al correr el código se abre una ventana en blanco y la consola empieza a imprimir mensajes. De hecho como entrará en un ciclo infinito debemos detenerlo cerrando la ventana blanca que se abre.
Este es el resultado de correr el código con "cargo run":
Y este es el resultado de correrlo usando "cargo run --release":
Al ejecutarlo con "cargo run --release" el compilador hace optimizaciones que tardan más tiempo en compilar pero el programa corre más rápido y por lo tanto el juego corre más rápido.
Navegación:
Primera parte
Siguiente parte
Parte anterior
Fuente (inglés):
- Hello ggez https://github.com/ggez/ggez/blob/master/docs/guides/HelloGgez.md
- Game programming patterns: http://www.gameprogrammingpatterns.com/
- Rustris Postmortem: Making tetris with ggez https://dale.io/blog/rustris-post-mortem.html
- Making a snake game in rust https://www.youtube.com/watch?v=HCwMb0KslX8
- Rustlang Project: Snake game https://www.youtube.com/watch?v=DnT_7M7L7vo
- Playing with GGEZ https://www.youtube.com/watch?v=oBdZtank14A