jueves, 26 de diciembre de 2019

Creando videojuegos en Rust lang (Parte 33)

En esta entrada usaré las opciones de ggez en cuestión a administración de archivos. En las entradas anteriores se mostraba como los archivos de recursos que usamos en el juego (los sonidos e imágenes) se tenían que colocar manualmente dentro de la carpeta "resources" que a su vez se tenía que colocar dentro de la carpeta "debug" que se genera cuando compilamos el programa con "cargo run". Esto sólo se cumple si compilamos en modo "debug" usando "cargo run", la carpeta sería "release" si compilamos en modo "release" usando "cargo run --release".

Estos son los archivos que teníamos en el ejemplo de la entrada anterior cuando compilamos en modo debug usando "cargo run":
La carpeta "target\debug\resources"
Si no movemos los archivos a este lugar manualmente entonces nuestro programa no podrá encontrar los recursos y mostará el siguiente mensaje de error:
Mensaje de error "El sistema no puede encontrar el archivo especificado" y menciona la carpeta "target/release/resouces"
Para evitar requerir que el usuario mueva estos archivos al lugar adecuado y manejar archivos de una mejor forma "ggez" nos proporciona con funcionalidades de manejo de archivos. Estas funciones las podemos ver ejemplificadas en los ejemplos en GitHub: https://github.com/ggez/ggez/blob/master/examples/files.rs. Las líneas que debemos de agregar son las líneas que agregan la carpeta de nuestro programa y una carpeta "resources". Este es el bloque de código que contiene las líneas que debemos agregar:

    let mut cb = ContextBuilder::new("ggez_files_example", "ggez");

    // We add the CARGO_MANIFEST_DIR/resources to the filesystems paths so
    // we we look in the cargo project for files.
    // Using a ContextBuilder is nice for this because it means that
    // it will look for a conf.toml or icon file or such in
    // this directory when the Context is created.
    if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
        let mut path = path::PathBuf::from(manifest_dir);
        path.push("resources");
        println!("Adding path {:?}", path);
        cb = cb.add_resource_path(path);
    }

    let (ctx, _) = &mut cb.build()?;

Ponemos nuestros archivos en la carpeta "resources" en la carpeta de nuestro proyecto:
Ahora podemos dejar la carpeta "resources" en donde está nuestro archivo "Cargo.toml"
Entonces los cambios que debemos de hacer en nuestro código son los siguientes:

  • Hay que agregar las referencias a "use std::env;", "use std::path;"
    • use std::env;
      use std::path;
      
  • El resultado es entonces que nuestro código en nuestra función "main" ahora es el siguiente:
    • let mut constructor_de_contexto = ContextBuilder::new(
              "hello_ggez", "autor")
          .conf(configuracion);
      if let Ok(directorio_de_cargo) = env::var(
              "CARGO_MANIFEST_DIR") {
          let mut carpeta_cargo = path::PathBuf::from(
              directorio_de_cargo);
          carpeta_cargo.push("resources");
          println!("Agregando carpeta de recursos {:?}", 
              carpeta_cargo);
          constructor_de_contexto = constructor_de_contexto
              .add_resource_path(carpeta_cargo);
      }
      let (ref mut contexto, 
           ref mut bucle_de_juego) = constructor_de_contexto
           .build().unwrap();
      
Y como resultado de los cambios anteriores este es nuestro código con los cambios más recientes:


  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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
use ggez::{conf, ContextBuilder, Context, 
           event, graphics, GameResult};
use std::time::{Duration, Instant};

use std::env;
use std::path;

use rand::prelude::*;

use std::collections::VecDeque;

use ggez::audio;
use ggez::audio::SoundSource;

use ggez::graphics::Text;

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;

#[derive(PartialEq)]
enum Direccion {
    Arriba,
    Abajo,
    Izquierda,
    Derecha,
}

#[derive(PartialEq)]
enum Estado {
    Jugando,
    GameOver
}

#[derive(PartialEq)]
enum Fondo {
    Estado1,
    Estado2
}

struct EstadoDelJuego {
    jugador_x: i16,
    jugador_y: i16,
    cuerpo: VecDeque<(i16, i16)>,
    direccion: Direccion,
    estado: Estado,
    comida_x: i16,
    comida_y: i16,
    sonido: audio::Source,
    puntaje: i16,
    imagen_comida: graphics::Image,
    imagen_serpiente: graphics::Image,
    ultima_actualizacion: std::time::Instant,
    spritebatch: graphics::spritebatch::SpriteBatch,
    fondo: Fondo
}

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) {
            if self.fondo == Fondo::Estado1 {
                self.fondo = Fondo::Estado2;
            } else {
                self.fondo = Fondo::Estado1;
            }
            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;
                } else {
                    for parte in self.cuerpo.iter() {
                        if parte.0 == self.jugador_x &&
                           parte.1 == self.jugador_y {
                            self.estado = Estado::GameOver;
                            self.jugador_y = self.jugador_y + 1;
                            break;
                        }
                    }
                }},
                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;
                } else {
                    for parte in self.cuerpo.iter() {
                        if parte.0 == self.jugador_x &&
                           parte.1 == self.jugador_y {
                            self.estado = Estado::GameOver;
                            self.jugador_y = self.jugador_y - 1;
                            break;
                        }
                    }
                }},
                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;
                } else {
                    for parte in self.cuerpo.iter() {
                        if parte.0 == self.jugador_x &&
                           parte.1 == self.jugador_y {
                            self.estado = Estado::GameOver;
                            self.jugador_x = self.jugador_x + 1;
                            break;
                        }
                    }
                }},
                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;
                } else {
                    for parte in self.cuerpo.iter() {
                        if parte.0 == self.jugador_x &&
                           parte.1 == self.jugador_y {
                            self.estado = Estado::GameOver;
                            self.jugador_x = self.jugador_x - 1;
                            break;
                        }
                    }
                }}
            }
            if self.jugador_x == self.comida_x &&
               self.jugador_y == self.comida_y {
                let _correr_sonido = self.sonido.play_detached();
                let mut rng = rand::thread_rng();
                let mut ini_comida_x = rng.gen_range::<i16, i16, i16>(0, 
                    DIMENSION_DEL_TABLERO.0);
                let mut ini_comida_y = rng.gen_range::<i16, i16, i16>(0, 
                    DIMENSION_DEL_TABLERO.1);
                loop {
                    if self.jugador_x != ini_comida_x &&
                       self.jugador_y != ini_comida_y {
                        let mut ocupado = false;
                        for parte_de_cuerpo in self.cuerpo.iter() {
                            if parte_de_cuerpo.0 == ini_comida_x &&
                               parte_de_cuerpo.1 == ini_comida_y {
                                ocupado = true;
                                break;
                            }
                        }
                        if ocupado == false {
                            break;
                        }
                    }
                    ini_comida_x = rng.gen_range::<i16, i16, i16>(0, 
                        DIMENSION_DEL_TABLERO.0);
                    ini_comida_y = rng.gen_range::<i16, i16, i16>(0, 
                        DIMENSION_DEL_TABLERO.1);
                }
                self.comida_x = ini_comida_x;
                self.comida_y = ini_comida_y;
                self.cuerpo.push_front((self.jugador_x, self.jugador_y));
                self.puntaje = self.puntaje + 1;
            } else {
                self.cuerpo.pop_back();
                self.cuerpo.push_front((self.jugador_x, self.jugador_y));
            }
            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());
        
        // Dibujar el fondo
        for x in 0..DIMENSION_DEL_TABLERO.0 {
            for y in 0..DIMENSION_DEL_TABLERO.1 {
                let x = x as f32;
                let y = y as f32;
                if self.fondo == Fondo::Estado1 {
                    let p = graphics::DrawParam::new()
                    .src(ggez::graphics::Rect::new(
                     0.0 as f32, 
                     0.0 as f32,
                     0.5 as f32,
                     1 as f32))
                    .dest(ggez::mint::Point2{x : x * 
                     DIMENSION_DE_CELDAS_DEL_TABLERO.0 as f32, 
                                             y : y * 
                     DIMENSION_DE_CELDAS_DEL_TABLERO.1 as f32});
                    self.spritebatch.add(p);
                } else {
                    let p = graphics::DrawParam::new()
                    .src(ggez::graphics::Rect::new(
                    0.5 as f32, 
                    0.0 as f32,
                    0.5 as f32,
                    1 as f32))
                    .dest(ggez::mint::Point2{x : x * 
                     DIMENSION_DE_CELDAS_DEL_TABLERO.0 as f32, 
                                             y : y * 
                     DIMENSION_DE_CELDAS_DEL_TABLERO.1 as f32});
                    self.spritebatch.add(p);
                }
            }

        }
        let param = graphics::DrawParam::new()
            .dest(ggez::mint::Point2 {
                x: 0.0,
                y: 0.0
            })
            .offset(ggez::mint::Point2{ x: 0.0, y: 0.0});
        graphics::draw(contexto, &self.spritebatch, param)?;
        self.spritebatch.clear();
        
        // Dibujar el cuerpo de la serpiente
        for parte_del_cuerpo in self.cuerpo.iter() {
            let rectangulo_cuerpo = ggez::graphics::Rect::new(
                (parte_del_cuerpo.0 *
                 DIMENSION_DE_CELDAS_DEL_TABLERO.0) as f32, 
                (parte_del_cuerpo.1 *
                 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_cuerpo = graphics::Mesh::new_rectangle(
                contexto,
                graphics::DrawMode::fill(),
                rectangulo_cuerpo,
                [9.0, 0.1, 0.0, 1.0].into())?;
            graphics::draw(contexto, 
                &grafico_de_cuerpo, 
                (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?;
        }
        // Dibujar el jugador
        let rectangulo_jugador = 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_jugador = graphics::Mesh::new_rectangle(
            contexto,
            graphics::DrawMode::fill(),
            rectangulo_jugador,
            [0.3, 0.3, 0.0, 1.0].into())?;
        graphics::draw(contexto, 
            &grafico_de_jugador, 
            (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?;
        // Dibujar la comida
        let rectangulo_comida = ggez::graphics::Rect::new(
            (self.comida_x *
             DIMENSION_DE_CELDAS_DEL_TABLERO.0) as f32, 
            (self.comida_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_comida = graphics::Mesh::new_rectangle(
            contexto,
            graphics::DrawMode::fill(),
            rectangulo_comida,
            [0.0, 0.0, 1.0, 1.0].into())?;
        graphics::draw(contexto, 
            &grafico_de_comida, 
            (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?;
        graphics::draw(contexto, 
            &self.imagen_comida, 
            (ggez::mint::Point2 { 
             x: (self.comida_x *
             DIMENSION_DE_CELDAS_DEL_TABLERO.0) as f32, 
             y: (self.comida_y *
             DIMENSION_DE_CELDAS_DEL_TABLERO.1) as f32 },))?;
        if self.estado == Estado::GameOver {
            graphics::draw(
                contexto,
                &self.imagen_serpiente,
                (ggez::mint::Point2 { x: 112.0, y: 112.0 },)
            )?;
            let texto_puntaje = Text::new(format!(
                "Game Over\nPuntaje:{}", self.puntaje));
            graphics::draw(
                contexto,
                &texto_puntaje,
                (ggez::mint::Point2 { x: 112.0, y: 112.0 },)
            )?;
        }
        // Mandando al generador de gráficos
        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 => {
                if self.direccion != Direccion::Abajo {
                    self.direccion = Direccion::Arriba
                }
            },
            ggez::event::KeyCode::Down => {
                if self.direccion != Direccion::Arriba {
                    self.direccion = Direccion::Abajo
                }
            },
            ggez::event::KeyCode::Left => {
                if self.direccion != Direccion::Derecha {
                    self.direccion = Direccion::Izquierda
                }
            },
            ggez::event::KeyCode::Right => {
                if self.direccion != Direccion::Izquierda {
                    self.direccion = Direccion::Derecha
                }
            },
            _ => (),
        }
        ()
    }
}

fn main() {
    let mut rng = rand::thread_rng();
    let mut ini_comida_x = rng.gen_range::<i16, i16, i16>(0, 
        DIMENSION_DEL_TABLERO.0);
    let mut ini_comida_y = rng.gen_range::<i16, i16, i16>(0, 
        DIMENSION_DEL_TABLERO.1);
    loop {
        if DIMENSION_DEL_TABLERO.0 / 4 != ini_comida_x &&
           DIMENSION_DEL_TABLERO.1 / 2 != ini_comida_y {
            break;
        }
        ini_comida_x = rng.gen_range::<i16, i16, i16>(0, 
            DIMENSION_DEL_TABLERO.0);
        ini_comida_y = rng.gen_range::<i16, i16, i16>(0, 
            DIMENSION_DEL_TABLERO.1);
    }
    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 mut constructor_de_contexto = ContextBuilder::new(
            "hello_ggez", "autor")
        .conf(configuracion);
    if let Ok(directorio_de_cargo) = env::var(
            "CARGO_MANIFEST_DIR") {
        let mut carpeta_cargo = path::PathBuf::from(
            directorio_de_cargo);
        carpeta_cargo.push("resources");
        println!("Agregando carpeta de recursos {:?}", 
            carpeta_cargo);
        constructor_de_contexto = constructor_de_contexto
            .add_resource_path(carpeta_cargo);
    }
    let (ref mut contexto, 
         ref mut bucle_de_juego) = constructor_de_contexto
         .build().unwrap();
    let fondo = graphics::Image::new(contexto,
                    "/fondo.png").unwrap();
    let estado_del_juego = &mut EstadoDelJuego {
        jugador_x: DIMENSION_DEL_TABLERO.0 / 4,
        jugador_y: DIMENSION_DEL_TABLERO.1 / 2,
        cuerpo: VecDeque::new(),
        direccion: Direccion::Derecha,
        estado: Estado::Jugando,
        comida_x: ini_comida_x,
        comida_y: ini_comida_y,
        sonido: audio::Source::new(
            contexto, "/sonido.ogg").unwrap(),
        puntaje: 0i16,
        imagen_comida: graphics::Image::new(
            contexto, "/comida.png").unwrap(),
        imagen_serpiente: graphics::Image::new(
            contexto, "/serpiente.png").unwrap(),
        ultima_actualizacion: Instant::now(),
        spritebatch: graphics::spritebatch::SpriteBatch::new(
                     fondo),
        fondo: Fondo::Estado1
    };
    event::run(contexto, bucle_de_juego, 
        estado_del_juego).unwrap();
}

No hay comentarios.:

Publicar un comentario