一个简单的 rust项目 flappy bird

一个非常简单的小项目。
看到了杨旭大佬的教学视频,自己跟着实现了一下,完善了一下游戏逻辑。
通过空格键进行控制。
游戏中可按 P 键 暂停/恢复 游戏

项目结构

·
├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├──bird/
│   │  ├── bird.rs
│   │  └── mod.rs
│   ├──game/
│   │   ├── game.rs
│   │   └── mod.rs
│   └──obstacles/
│       ├──obstacle.rs
│       └── mod.rs
  • game.rs 负责游戏的逻辑、控制、等内容。
  • bird.rs 负责小鸟本身的实现。
  • obstacle.rs 负责障碍物的实现。

三个mod.rs 文件

// game/mod.rs
pub mod game;

// bird/mod.rs
pub mod bird;

// obstacles/mod.rs
pub mod obstacle;

main.rs

use bracket_lib::prelude::{main_loop, BError, BTermBuilder};

pub mod bird;
pub mod game;
pub mod obstacles;

fn main() -> BError {
    // 创建窗口
    let ctx = BTermBuilder::simple80x50()
        .with_title("Flappy Bird")
        .build()
        .unwrap();

    // 创建游戏内容
    let game = game::game::State::new();

    // 初始化游戏 loop
    main_loop(ctx, game)
}

game.rs

use crate::bird::bird;
use crate::obstacles::obstacle;
use bracket_lib::{
    prelude::{BTerm, GameState},
    terminal::{VirtualKeyCode, LIGHT_BLUE},
};
use std::collections::LinkedList;

/// 游戏窗口的宽
const GAME_WIDTH: u32 = 80;
/// 游戏窗口的高
const GAME_HEIGHT: u32 = 50;
/// 游戏周期
const PERIOD: f32 = 120.0;

/// 游戏模式
enum GameMode {
    /// 游戏中
    Playing,
    /// 游戏结束
    End,
    /// 游戏暂停
    Paused,
    /// 菜单页面
    Menu,
}

/// 游戏 State
pub struct State {
    /// 当前模式
    mode: GameMode,
    /// 得分
    score: u32,
    /// 小鸟
    bird: bird::Bird,
    /// 等待时间
    waite_time: f32,
    /// 障碍物列表,因为需要从头尾两处进行操作,所以选择使用双向链表
    obstacle_list: LinkedList<obstacle::OBstacle>,
}

/// bracket_lib::prelude::State 的实现
impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        match self.mode {
            GameMode::Playing => self.paly_control(ctx),
            GameMode::End => self.end_control(ctx),
            GameMode::Menu => self.menu_control(ctx),
            GameMode::Paused => self.paused_control(ctx),
        }
    }
}

impl State {
    /// 游戏状态初始化
    pub fn new() -> Self {
        let mut obstacle_list = LinkedList::new();
        obstacle_list.push_back(obstacle::OBstacle::new(0, 35, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 50, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 65, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 80, GAME_HEIGHT));
        Self {
            mode: GameMode::Menu,
            score: 0,
            bird: bird::Bird::new(GAME_WIDTH, GAME_HEIGHT),
            waite_time: 0.0,
            obstacle_list,
        }
    }

    /// 重置游戏内容的方法
    fn reset(&mut self) {
        self.mode = GameMode::Playing;
        self.waite_time = 0.0;
        self.bird = bird::Bird::new(GAME_WIDTH, GAME_HEIGHT);
        self.score = 0;
        let mut obstacle_list = LinkedList::new();
        obstacle_list.push_back(obstacle::OBstacle::new(0, 35, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 50, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 65, GAME_HEIGHT));
        obstacle_list.push_back(obstacle::OBstacle::new(0, 80, GAME_HEIGHT));
        self.obstacle_list = obstacle_list;
    }

    /// 游戏中的控制方法
    fn paly_control(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.set_bg(GAME_WIDTH, GAME_HEIGHT, LIGHT_BLUE);

        // tick 函数的调用周期 ctx.frame_time_ms =33
        self.waite_time += ctx.frame_time_ms;

        // 达到等待时间了,需要执行游戏内容
        if self.waite_time >= PERIOD {
            self.bird.gravity_and_move();
            self.move_obstacles();
            self.pass_obstacles();
            self.waite_time = 0.0;
        }

        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::P => self.mode = GameMode::Paused,
                VirtualKeyCode::Space => {
                    self.bird.flap();
                    self.waite_time = 0.0;
                }
                VirtualKeyCode::Q | VirtualKeyCode::Escape => ctx.quit(),
                _ => (),
            }
        }
        self.bird.draw(ctx);

        for obt in &self.obstacle_list {
            obt.draw(GAME_HEIGHT, ctx)
        }
        ctx.print(0, 0, format!("Score :{}", self.score));
        ctx.print(0, 1, "Space to flap");
        ctx.print(0, 2, "(Q/Esc) Quit game)");

        if self.bird.bird_out(GAME_HEIGHT) {
            self.mode = GameMode::End
        }
    }

    /// 移动障碍物
    fn move_obstacles(&mut self) {
        for obt in &mut self.obstacle_list {
            obt.move_forward(1);
        }
    }

    /// 通过障碍物检测,并记分
    fn pass_obstacles(&mut self) {
        let first_obt = self.obstacle_list.front().unwrap();
        if first_obt.obstacle_x() <= self.bird.bird_x() as u32 {
            let half_sie = first_obt.obstacle_size() / 2;
            let first_top = first_obt.obstacle_gap_y() - half_sie;
            let first_bottom = first_obt.obstacle_gap_y() + half_sie;
            let dragon_y = self.bird.bird_y() as u32;

            if dragon_y > first_top && dragon_y < first_bottom {
                self.score += 1;
            } else {
                self.mode = GameMode::End;
            }

            self.obstacle_list.pop_front();
            self.obstacle_list
                .push_back(obstacle::OBstacle::new(self.score, 80, GAME_HEIGHT));
        }
    }

    /// 游戏结束时的控制方法
    fn end_control(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(6, format!("You are DEAD ! with {} score. ", self.score));
        ctx.print_centered(8, " (R)  Restart game");
        ctx.print_centered(9, " (Q/Esc) Exit ");

        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::R => {
                    self.reset();
                    self.mode = GameMode::Playing;
                }
                VirtualKeyCode::Q | VirtualKeyCode::Escape => ctx.quitting = true,
                _ => (),
            }
        }
    }

    /// 菜单状态的控制方法
    fn menu_control(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(6, "Welcome to flappy dragon ");
        ctx.print_centered(8, " (S) Start game");
        ctx.print_centered(9, " (P) Paused game");
        ctx.print_centered(10, " (Q/Esc) Exit ");

        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::S => self.mode = GameMode::Playing,
                VirtualKeyCode::Q | VirtualKeyCode::Escape => ctx.quit(),
                _ => (),
            }
        }
    }

    /// 游戏暂停时的控制方法
    fn paused_control(&mut self, ctx: &mut BTerm) {
        ctx.print_centered(0, format!("Score :{}", self.score));
        ctx.print_centered(7, "Game Paused, (P) to return game!");
        self.bird.draw(ctx);
        if let Some(key) = ctx.key {
            match key {
                VirtualKeyCode::P => self.mode = GameMode::Playing,
                VirtualKeyCode::Q | VirtualKeyCode::Escape => ctx.quit(),
                _ => (),
            }
        }
    }
}

bird.rs

use bracket_lib::prelude::BTerm;
use bracket_lib::terminal::{to_cp437, BLACK, YELLOW_GREEN};

/// 小鸟
pub struct Bird {
    /// x坐标
    x: f64,
    /// y坐标
    y: f64,
    /// 下落速度
    velocity: f64,
}

impl Bird {
    /// 初始化小鸟
    pub fn new(game_width: u32, game_height: u32) -> Self {
        Self {
            x: (game_width / 5) as f64,
            y: (game_height / 2) as f64,
            velocity: 0.1,
        }
    }

    /// 绘制
    pub fn draw(&self, ctx: &mut BTerm) {
        ctx.set(
            self.x as u32,
            self.y as u32,
            YELLOW_GREEN,
            BLACK,
            to_cp437('&'),
        );
    }

    /// 获取当前的 x 坐标
    pub fn bird_x(&self) -> f64 {
        self.x
    }

    /// 获取当前的 y 坐标
    pub fn bird_y(&self) -> f64 {
        self.y
    }

    /// 自由下落
    pub fn gravity_and_move(&mut self) {
        self.velocity += 0.1;
        self.y += self.get_velocity();
    }

    /// 点击空格向上
    pub fn flap(&mut self) {
        self.y -= 2.0;
        self.velocity = 0.1;
    }

    /// 是否超出边界
    pub fn bird_out(&self, max_y: u32) -> bool {
        self.y as u32 > max_y || self.y < 0.0
    }

    /// 获取当前下落的速度,限制到了2.0
    fn get_velocity(&mut self) -> f64 {
        if self.velocity > 2.0 {
            self.velocity = 2.0
        }
        self.velocity
    }
}

obstacle.rs

use bracket_lib::{
    prelude::BTerm,
    random::RandomNumberGenerator,
    terminal::{to_cp437, BLACK, RED},
};

/// 障碍物
pub struct OBstacle {
    /// 障碍物的横坐标
    x: u32,
    /// 缝隙高度
    size: u32,
    /// 障碍物的中心点
    gap_y: u32,
}

impl OBstacle {
    /// 障碍物初始化
    ///
    /// *score 当前得分
    ///
    /// *start_x 初始x坐标
    ///
    /// *screen_height 游戏窗口高度
    pub fn new(score: u32, start_x: u32, screen_height: u32) -> Self {
        let mut random = RandomNumberGenerator::new();

        let size;
        if score > 60 {
            size = 2
        } else {
            size = u32::max(2, 20 - score / 3);
        }

        let half_size = size / 2 + 1;
        Self {
            x: start_x,
            size,
            gap_y: random.range(half_size, screen_height - half_size),
        }
    }

    /// 向前移动
    ///
    /// *distance 向前移动的距离
    pub fn move_forward(&mut self, distance: u32) {
        self.x -= distance
    }

    pub fn obstacle_x(&self) -> u32 {
        self.x
    }

    pub fn obstacle_size(&self) -> u32 {
        self.size
    }

    pub fn obstacle_gap_y(&self) -> u32 {
        self.gap_y
    }

    /// 绘制
    pub fn draw(&self, screen_height: u32, ctx: &mut BTerm) {
        let screen_x = self.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('#'));
        }
    }
}

Cargo.toml

[package]
name = "flappy_bird"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bracket-lib = "0.8.7"

Rust官网
Rust 中文社区

热门相关:最强狂兵   人间欢喜   天启预报   网游之逆天飞扬   第一神算:纨绔大小姐