Практическое знакомство с Rust (4) — птица летит

Rust
Практическое знакомство с Rust (4) — птица летит

Прежде чем продолжить разработку игры из предыдущей статьи, давайте взглянем на готовый продукт:

fly

Добавлены три основные роли:

  1. Персонаж '@' действует как птица и отвечает за перемещение вверх и вниз, чтобы избежать встречных препятствий - стен;
  2. Это наше препятствие, и препятствие со временем продолжает двигаться влево от правой части экрана;
  3. Также есть подсчет очков, пока птица пройдет через препятствия, она получит одно очко, если не убежит, то игра окончена.

Adding the Player

Символ '@' действует как птица

Функция птицы заключается в том, что ей нужно двигаться вверх и вниз, и при нормальных обстоятельствах она находится в состоянии «свободной посадки».Когда мы нажимаем пробел, она будет летать вверх, а не падать вниз.

Как и раньше, используйтеstructструктура для представленияPlayer, в основном положение координат и значение изменения ускорения движения вверх и вниз:

struct Player {
    x: i32,
    y: i32,
    velocity: f32,
}

Сначала установите один@Эффект отображения персонажа:

impl Player {
    fn new(x: i32, y: i32) -> Self {
        Player {
            x,
            y,
            velocity: 0.0,
        }
    }

    fn render(&mut self, ctx: &mut BTerm) {
        ctx.set(
            0,
            self.y,
            YELLOW,
            BLACK,
            to_cp437('@')
        );
    }
}

Это относительно легко понять в последующем процессе кода, чтобы сосредоточиться на объясненииbracket-libдвигатель.

Следующим шагом является определение двух действий, в основном изменение ускорения, я думаю, вы можете понять это, взглянув на код:

fn gravity_and_move(&mut self) {
    if self.velocity < 2.0 {
        self.velocity += 0.2;
    }
    self.y += self.velocity as i32;
    self.x += 1;
    
    if self.y < 0 {
        self.y = 0;
    }
}

fn flap(&mut self) {
    self.velocity = -2.0;
}

Creating Obstacles

создавать барьеры

Препятствия, основное внимание уделяется случайному появлению зазора и размеру зазора.

struct Obstacle {
    x: i32,
    gap_y: i32,
    size: i32,
}

В этой статье размер разрыва связан со счетом: чем больше очков, тем меньше разрыв, что также увеличивает сложность игры.newфункция:

impl Obstacle {
    fn new(x: i32, score: i32) -> Self {
        let mut random = RandomNumberGenerator::new();
        Obstacle {
            x,
            gap_y: random.range(10, 40),
            size: i32::max(2, 20 -score)
        }
    }

    fn render(&mut self, ctx: &mut BTerm, player_x: i32) {
        let screen_x = self.x - player_x;
        let half_size = self.size / 2;

        for y in 0..self.gap_y - half_size {
            ctx.set(
                screen_x,
                y,
                RED,
                BLACK,
                to_cp437('/'),
            );
        }

        for y in self.gap_y + half_size..SCREEN_HEIGHT {
            ctx.set(
                screen_x,
                y,
                RED,
                BLACK,
                to_cp437('/'),
            );
        }
    }
}

Конструкция стены в основном разделена на две части по размеру зазора для управления координатой y, а значение x каждого кадра определяется его собственной координатой x и координатами птицы для достижения эффекта. постоянного приближения к птице.

Также необходимо добавить логику встречи с птицами и препятствиями:

fn hit_obstacle(&self, player: &Player) -> bool {
    let half_size = self.size / 2;
    let does_x_match = player.x == self.x;
    let player_above_gap = player.y < self.gap_y - half_size;
    let player_below_gap = player.y > self.gap_y + half_size;
    
    does_x_match && (player_above_gap || player_below_gap)
}

Этот код легко понять, поэтому я не буду его объяснять.

Keeping Score

логика подсчета очков

Логика подсчета очков относительно проста: если x-координата птицы > x-координата препятствия, это означает, что птица «пролетела» над препятствием и не попала в препятствие, балл + 1 и новое препятствие сбрасывается. Объект:

fn play(&mut self, ctx: &mut BTerm) {
    // TODO: Fill in this stub later
    ctx.cls_bg(NAVY);
    self.frame_time += ctx.frame_time_ms;
    
    if self.frame_time > FRAME_DURATION {
        self.frame_time = 0.0;
        self.player.gravity_and_move();
    }

    if let Some(VirtualKeyCode::Space) = ctx.key {
        self.player.flap();
    }
    self.player.render(ctx);
    ctx.print(0, 0, "按住空格保持飞翔");
    ctx.print(0, 1, &format!("得分:{}", self.score));

    self.obstacle.render(ctx, self.player.x);
    if self.player.x > self.obstacle.x {
        self.score += 1;
        self.obstacle = Obstacle::new(self.player.x + SCREEN_WIDTH, self.score);
    }

    if self.player.y > SCREEN_HEIGHT || self.obstacle.hit_obstacle(&self.player) {
        self.mode = GameMode::End;
    }
}

Остальные параметры помогают в доработке логики, весь код я выложу ниже, и все поймут его, когда посмотрят:

use bracket_lib::prelude::*;

const SCREEN_WIDTH: i32 = 80;
const SCREEN_HEIGHT: i32 = 50;
const FRAME_DURATION: f32 = 75.0;
struct Player {
    x: i32,
    y: i32,
    velocity: f32,
}

impl Player {
    fn new(x: i32, y: i32) -> Self {
        Player {
            x,
            y,
            velocity: 0.0,
        }
    }

    fn render(&mut self, ctx: &mut BTerm) {
        ctx.set(
            0,
            self.y,
            YELLOW,
            BLACK,
            to_cp437('@')
        );
    }

    fn gravity_and_move(&mut self) {
        if self.velocity < 2.0 {
            self.velocity += 0.2;
        }
        self.y += self.velocity as i32;
        self.x += 1;
        
        if self.y < 0 {
            self.y = 0;
        }
    }

    fn flap(&mut self) {
        self.velocity = -2.0;
    }
}

struct Obstacle {
    x: i32,
    gap_y: i32,
    size: i32,
}

impl Obstacle {
    fn new(x: i32, score: i32) -> Self {
        let mut random = RandomNumberGenerator::new();
        Obstacle {
            x,
            gap_y: random.range(10, 40),
            size: i32::max(2, 20 -score)
        }
    }

    fn render(&mut self, ctx: &mut BTerm, player_x: i32) {
        let screen_x = self.x - player_x;
        let half_size = self.size / 2;

        for y in 0..self.gap_y - half_size {
            ctx.set(
                screen_x,
                y,
                RED,
                BLACK,
                to_cp437('/'),
            );
        }

        for y in self.gap_y + half_size..SCREEN_HEIGHT {
            ctx.set(
                screen_x,
                y,
                RED,
                BLACK,
                to_cp437('/'),
            );
        }
    }

    fn hit_obstacle(&self, player: &Player) -> bool {
        let half_size = self.size / 2;
        let does_x_match = player.x == self.x;
        let player_above_gap = player.y < self.gap_y - half_size;
        let player_below_gap = player.y > self.gap_y + half_size;
        
        does_x_match && (player_above_gap || player_below_gap)
    }
}

enum GameMode {
    Menu,
    Playing,
    End,
}

struct State {
    player: Player,
    frame_time: f32,
    obstacle: Obstacle,
    mode: GameMode,
    score: i32,
}

impl State {
    fn new() -> Self {
        State {
            player: Player::new(5, 25),
            frame_time: 0.0,
            obstacle: Obstacle::new(SCREEN_WIDTH, 0),
            mode: GameMode::Menu,
            score: 0,
        }
    }

    fn restart(&mut self) {
        self.player = Player::new(5, 25);
        self.frame_time = 0.0;
        self.mode = GameMode::Playing;
    }

    fn main_menu(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(5, "Welcome to Flappy Dragon");
        ctx.print_centered(8, "(P) Play Game");
        ctx.print_centered(9, "(Q) Quit Game");

        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::P => self.restart(),
                VirtualKeyCode::Q => ctx.quitting = true,
                _ => {}
            }
        }
        self.player = Player::new(5, 25);
        self.frame_time = 0.0;
        self.obstacle = Obstacle::new(SCREEN_WIDTH, 0);
        self.mode = GameMode::Playing;
        self.score = 0;
    }

    fn dead(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(5, "You are dead!");
        ctx.print_centered(6, &format!("You earned {} points", self.score));
        ctx.print_centered(8, "(P) Play Again");
        ctx.print_centered(9, "(Q) Quit Game");

        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::P => self.restart(),
                VirtualKeyCode::Q => ctx.quitting = true,
                _ => {}
            }
        }
    }

    fn play(&mut self, ctx: &mut BTerm) {
        // TODO: Fill in this stub later
        ctx.cls_bg(NAVY);
        self.frame_time += ctx.frame_time_ms;
        
        if self.frame_time > FRAME_DURATION {
            self.frame_time = 0.0;
            self.player.gravity_and_move();
        }

        if let Some(VirtualKeyCode::Space) = ctx.key {
            self.player.flap();
        }
        self.player.render(ctx);
        ctx.print(0, 0, "按住空格保持飞翔");
        ctx.print(0, 1, &format!("得分:{}", self.score));

        self.obstacle.render(ctx, self.player.x);
        if self.player.x > self.obstacle.x {
            self.score += 1;
            self.obstacle = Obstacle::new(self.player.x + SCREEN_WIDTH, self.score);
        }

        if self.player.y > SCREEN_HEIGHT || self.obstacle.hit_obstacle(&self.player) {
            self.mode = GameMode::End;
        }
    }
}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        // ctx.cls();
        // ctx.print(1, 1, "Hello, Bracket Terminal!");
        match self.mode {
            GameMode::Menu => self.main_menu(ctx),
            GameMode::End => self.dead(ctx),
            GameMode::Playing => self.play(ctx),
        }
    }
}

fn main() ->BError {
    println!("Hello, world!");

    let context = BTermBuilder::simple80x50()
        .with_title("Flappy Dragon")
        .build()?;
    
    main_loop(context, State::new())
}

Весь эффект операции как начало:

fly