sábado, 11 de mayo de 2019

Creando videojuegos en Rust lang (Parte 24)

Agregaremos al ejemplo una condición para que termine el juego, es decir, la condición de "game over". En las entradas anteriores el juego no termina y no hay condición para ganar ni condición para perder. También la posición del jugador no esta limitada. El jugador se puede salir del tablero/ventana. En esta entrada limitaremos el movimiento del jugador y agregaremos la condición para perder. Simplemente haremos que si el jugador va a moverse más allá del borde el juego termine y que su posición quede congelada donde estaba antes.

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

No hay comentarios.:

Publicar un comentario