Similar a la entrada anterior usaremos como referencia lo que hicimos anteriormente "hello_ggez05", para esta entrada haremos "cargo new hello_ggez06" y pondremos el archivo TOML similar a los ejemplos anteriores:
1 2 3 4 5 6 7 8 | [package] name = "hello_ggez06" version = "0.1.0" authors = ["MiUsuario <micorreo@correo.com>"] edition = "2018" [dependencies] ggez = "0.5.0-rc.1" |
Y ahora agregaremos la funcionalidad de "game over" y limitar el movimiento del jugador adentro de la ventana/tablero. Solo hay que agregar algunos bloques de código:
- Agregamos un enum para guardar el estado del juego. Por ahora tendremos 2 estados "Jugando" y "GameOver". Este enum lo usaremos en un "if" más adelante. Para poder usarlo fuera de "match" y usar el operador "==" debemos de derivar la propiedad "PartialEq" que es "Partial Equality" es decir "igualdad parcial". Esta propiedad nos permite usar los operadores de igualdad:
-
#[derive(PartialEq)] enum Estado { Jugando, GameOver }
- En el struct de "EstadoDelJuego" agregamos el estado del juego anterior. Así podemos tener la información del estado del juego actual. Con esto podremos actualizar el estado del juego según los eventos que sucedan en el juego:
-
struct EstadoDelJuego { jugador_x: i16, jugador_y: i16, direccion: Direccion, estado: Estado, ultima_actualizacion: std::time::Instant }
- Como cambiamos el struct de EstadoDeJuego ahora tenemos que cambiar el código de inicialización para ponerle un estado inicial. Escogeremos el estado "Jugando":
-
let estado_del_juego = &mut EstadoDelJuego { jugador_x: DIMENSION_DEL_TABLERO.0 / 4, jugador_y: DIMENSION_DEL_TABLERO.1 / 2, direccion: Direccion::Derecha, estado: Estado::Jugando, ultima_actualizacion: Instant::now() };
- La función "update" seguirá haciendo operaciones sólo cuando sea tiempo de actualizar. Y lo que agregaremos será una condición adicional al "if" en donde sólo haremos actualizaciones si el estado del juego dentro de nuestro struct EstadoDeJuego es "Jugando". También tenemos que agregar un condicional después de realizar el movimiento. Si nos encontramos en una celda más allá de la ventana o fuera de la ventana/tablero entonces actualizamos el EstadoDelJuego a "GameOver" y regresamos la posición de vuelta a dentro del tablero/ventana según su dirección. En resumen el interior de nuestra función "update" ahora se ve de la siguiente forma:
-
if (Instant::now() - self.ultima_actualizacion >= Duration::from_millis(MILISEG_POR_ACTUALIZACION)) & (self.estado == Estado::Jugando) { match self.direccion { Direccion::Arriba => { self.jugador_y = self.jugador_y - 1; if self.jugador_y < 0 { self.estado = Estado::GameOver; self.jugador_y = self.jugador_y + 1; }}, Direccion::Abajo => { self.jugador_y = self.jugador_y + 1; if self.jugador_y > DIMENSION_DEL_TABLERO.1 - 1 { self.estado = Estado::GameOver; self.jugador_y = self.jugador_y - 1; }}, Direccion::Izquierda => { self.jugador_x = self.jugador_x - 1; if self.jugador_x < 0 { self.estado = Estado::GameOver; self.jugador_x = self.jugador_x + 1; }}, Direccion::Derecha => { self.jugador_x = self.jugador_x + 1; if self.jugador_x > DIMENSION_DEL_TABLERO.0 - 1 { self.estado = Estado::GameOver; self.jugador_x = self.jugador_x - 1; }} } self.ultima_actualizacion = Instant::now(); } Ok(())
Y todo el código completo se ve de la siguiente forma en su estado actual:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | use ggez::{conf, ContextBuilder, Context, event, graphics, GameResult}; use std::time::{Duration, Instant}; const DIMENSION_DEL_TABLERO: (i16, i16) = (10, 10); const DIMENSION_DE_CELDAS_DEL_TABLERO: (i16, i16) = (32, 32); const DIMENSION_DE_VENTANA: (f32, f32) = ( DIMENSION_DEL_TABLERO.0 as f32 * DIMENSION_DE_CELDAS_DEL_TABLERO.0 as f32, DIMENSION_DEL_TABLERO.1 as f32 * DIMENSION_DE_CELDAS_DEL_TABLERO.1 as f32, ); const ACTUALIZACIONES_POR_SEGUNDO: f32 = 8.0; const MILISEG_POR_ACTUALIZACION: u64 = ( (1.0 / ACTUALIZACIONES_POR_SEGUNDO) * 1000.0) as u64; enum Direccion { Arriba, Abajo, Izquierda, Derecha, } #[derive(PartialEq)] enum Estado { Jugando, GameOver } struct EstadoDelJuego { jugador_x: i16, jugador_y: i16, direccion: Direccion, estado: Estado, ultima_actualizacion: std::time::Instant } impl ggez::event::EventHandler for EstadoDelJuego { fn update(&mut self, _contexto: &mut Context) -> GameResult<()> { if (Instant::now() - self.ultima_actualizacion >= Duration::from_millis(MILISEG_POR_ACTUALIZACION)) & (self.estado == Estado::Jugando) { match self.direccion { Direccion::Arriba => { self.jugador_y = self.jugador_y - 1; if self.jugador_y < 0 { self.estado = Estado::GameOver; self.jugador_y = self.jugador_y + 1; }}, Direccion::Abajo => { self.jugador_y = self.jugador_y + 1; if self.jugador_y > DIMENSION_DEL_TABLERO.1 - 1 { self.estado = Estado::GameOver; self.jugador_y = self.jugador_y - 1; }}, Direccion::Izquierda => { self.jugador_x = self.jugador_x - 1; if self.jugador_x < 0 { self.estado = Estado::GameOver; self.jugador_x = self.jugador_x + 1; }}, Direccion::Derecha => { self.jugador_x = self.jugador_x + 1; if self.jugador_x > DIMENSION_DEL_TABLERO.0 - 1 { self.estado = Estado::GameOver; self.jugador_x = self.jugador_x - 1; }} } self.ultima_actualizacion = Instant::now(); } Ok(()) } fn draw(&mut self, contexto: &mut Context) -> GameResult<()> { graphics::clear(contexto, [0.0, 1.0, 0.0, 1.0].into()); let rectangulo = ggez::graphics::Rect::new( (self.jugador_x * DIMENSION_DE_CELDAS_DEL_TABLERO.0) as f32, (self.jugador_y * DIMENSION_DE_CELDAS_DEL_TABLERO.1) as f32, DIMENSION_DE_CELDAS_DEL_TABLERO.0 as f32, DIMENSION_DE_CELDAS_DEL_TABLERO.1 as f32); let grafico_de_rectangulo = graphics::Mesh::new_rectangle( contexto, graphics::DrawMode::fill(), rectangulo, [0.3, 0.3, 0.0, 1.0].into())?; graphics::draw(contexto, &grafico_de_rectangulo, (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?; graphics::present(contexto)?; ggez::timer::yield_now(); Ok(()) } fn key_down_event( &mut self, _ctx: &mut Context, keycode: ggez::event::KeyCode, _keymod: ggez::event::KeyMods, _repeat: bool, ) { match keycode { ggez::event::KeyCode::Up => self.direccion = Direccion::Arriba, ggez::event::KeyCode::Down => self.direccion = Direccion::Abajo, ggez::event::KeyCode::Left => self.direccion = Direccion::Izquierda, ggez::event::KeyCode::Right => self.direccion = Direccion::Derecha, _ => (), } () } } fn main() { let estado_del_juego = &mut EstadoDelJuego { jugador_x: DIMENSION_DEL_TABLERO.0 / 4, jugador_y: DIMENSION_DEL_TABLERO.1 / 2, direccion: Direccion::Derecha, estado: Estado::Jugando, ultima_actualizacion: Instant::now() }; let mut configuracion = conf::Conf::new(); configuracion.window_setup = conf::WindowSetup::default() .title("Snake"); configuracion.window_mode = conf::WindowMode::default() .dimensions(DIMENSION_DE_VENTANA.0, DIMENSION_DE_VENTANA.1); 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(); } |
Si todo salió bien cuando lo corremos usando "cargo run" podemos ver el cuadro moviéndose a la derecha en un inicio y podemos cambiar su dirección. Al "chocar" con los límites del tablero o los límites de la ventana el juego se detiene:
En las siguientes entradas continuaremos creciendo este ejemplo según el ejemplo Snake de github.
Navegación:
Primera parte
Siguiente parte
Parte anterior
Fuentes (inglés):
- Página de Github de ggez con ejemplo usando Snake https://github.com/ggez/ggez/blob/master/examples/04_snake.rs
- Herramienta de formato del código para estas entradas (Rust): http://hilite.me/
No hay comentarios.:
Publicar un comentario