一个非常简单的小项目。
看到了杨旭大佬的教学视频,自己跟着实现了一下,完善了一下游戏逻辑。
通过空格键进行控制。
游戏中可按 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"
标签:flappy,self,ctx,pub,obstacle,fn,bird,rust
From: https://www.cnblogs.com/SantiagoZhang/p/17297670.html