Skip to content
On this page

bracket-lib

1、官网

官网地址:https://bfnightly.bracketproductions.com/rustbook

官网示例:https://bfnightly.bracketproductions.com/bracket-lib/what_is_it.html

2、安装

Cargo.toml

json
[dependencies]
bracket-lib = "~0.8"

3、基本使用

3.1、prelude

3.1.1、创建一个画布

js
use bracket_lib::prelude::*;

struct State {}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print(1, 1, "hello bracket!!!");
    }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Flappy Dragon")
        .build()?;
    main_loop(context, State{})
}

3.1.2、监听键盘事件

js
use bracket_lib::prelude::*;

struct State {}

impl GameState for State {
  fn tick(&mut self, ctx: &mut BTerm) {
      match ctx.key {
        Some(key) => println!("keycode: {:?}", key),
        _ => {},
      }
  }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50().with_title("title").build()?;
    main_loop(context, State{})
}

3.1.3、自定义窗口大小

js
let context = BTermBuilder::vga(100, 50).with_title("title").build()?;

3.1.4、画盒子

js
ctx.draw_box(39, 0, 20, 3, RGB::named(WHITE), RGB::named(BLACK));
ctx.printer(
    58,
    1,
    &format!("#[pink]FPS: #[]{}", ctx.fps),
    TextAlign::Right,
    None,
);
ctx.printer(
    58,
    2,
    &format!("#[pink]Frame Time: #[]{} ms", ctx.frame_time_ms),
    TextAlign::Right,
    None,
);

3.2、random模块

3.2.1、RandomNumberGenerator生成随机数

TIP

RandomNumberGenerator::new()使用的是随机种子

js
use bracket_lib::random::RandomNumberGenerator;

fn main() {
    let mut random = RandomNumberGenerator::new();
    println!("Hello, world!=={}", random.range(0, 10));
}

3.2.2、指定类型的随机数

js
use bracket_lib::random::RandomNumberGenerator;

fn main() {
    let mut random = RandomNumberGenerator::new();
    let res:f32 = random.rand();
    println!("Hello, world!=={}", res);
}

3.3、color

3.3.1、lerp文字颜色渐变

js
let col1 = RGB::named(CYAN);
let col2 = RGB::named(YELLOW);
let percent = self.y as f32 / 50.0;
let fg = col1.lerp(col2, percent);

ctx.print_color(
    1, 
    self.y, 
    fg, 
    RGB::named(BLACK), 
    "♫ ♪ Hello Bracket World ☺",
);
js
impl GameState for State {
  fn tick(&mut self, ctx: &mut BTerm) {
      let red = RGB::named(RED);
      let blue = RGB::named(YELLOW);
      for i in 1..80 {
          let percent = i as f32 / 80.0;
          let color = red.lerp(blue, percent);
          ctx.print_color(
              1, 
              i, 
              color, 
              RGB::named(BLACK), 
              "♫ ♪ Hello Bracket World ☺",
          );
      }
  }
}

3.3.2、lerpit迭代器渐变颜色

js
impl GameState for State {
  fn tick(&mut self, ctx: &mut BTerm) {
    let mut i = 0;
    for color in RgbLerp::new(RGB::named(RED), RGB::named(YELLOW), 20) {
        ctx.print_color(
            1, 
            i, 
            color, 
            RGB::named(BLACK), 
            "♫ ♪ Hello Bracket World ☺",
        );
        i += 1;
    }
  }
}

3.3.3、颜色渐变

js

5、示例

5.1、飞行的龙

js
use bracket_lib::prelude::*;

enum GameMode {
    Menu,
    Playing,
    End,
}

const SCREEN_WIDTH:i32 = 80;
const SCREEN_HEIGHT:i32 = 50;
const FRAME_DURATION:f32 = 60.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_mode(&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, // 障碍物y坐标
    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)
    }
}

struct State {
    mode: GameMode,
    player: Player,
    frame_time: f32, // 游戏刷新时间
    obstacle: Obstacle,
    score: i32, // 分数
}

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

    fn restart(&mut self) {
        self.mode = GameMode::Playing;
        self.player = Player::new(5, 25);
        self.frame_time = 0.0;
        // 重新初始化障碍物
        self.obstacle = Obstacle::new(SCREEN_WIDTH, 0);
        self.score = 0;
    }

    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,
              _ => {}
          }
      }
    }

    fn play(&mut self, ctx: &mut BTerm) {
        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_mode();
        }
        
        // 监听按键
        if let Some(VirtualKeyCode::Space) = ctx.key {
            self.player.flap();
        }

        // 渲染
        self.player.render(ctx);
        ctx.print(0, 0, "Press SPACE to flap.");
        ctx.print(0, 1, &format!("Score: {}", 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;
        }
    }

    fn dead(&mut self, ctx: &mut BTerm) {
        ctx.cls();
        ctx.print_centered(5, "Game Over!");
        ctx.print_centered(6, &format!("Your score: {}", 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,
              _ => {}
          }
        }
    }
}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        match self.mode {
            GameMode::Menu => self.main_menu(ctx),
            GameMode::Playing => self.play(ctx),
            GameMode::End => self.dead(ctx),
        }
    }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Flappy Dragon")
        .build()?;
    main_loop(context, State::new())
}

5.2、

Released under the MIT License.