Прежде чем продолжить разработку игры из предыдущей статьи, давайте взглянем на готовый продукт:
Добавлены три основные роли:
- Персонаж '@' действует как птица и отвечает за перемещение вверх и вниз, чтобы избежать встречных препятствий - стен;
- Это наше препятствие, и препятствие со временем продолжает двигаться влево от правой части экрана;
- Также есть подсчет очков, пока птица пройдет через препятствия, она получит одно очко, если не убежит, то игра окончена.
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())
}
Весь эффект операции как начало: