Hands-on Rust 学习之旅(3)—— 游戏基本流程

第三章开始我们就进入真正的 Game 学习阶段,在整个第三章我们能学到的知识主要有:

  1. game loop,
  2. basic program flow with a state machine,
  3. add a player,
  4. simulate gravity,
  5. and make the player’s dragon flap its wings.
  6. Finally, add obstaches and scorekeeping to the game.

这也是基本的游戏框架了。

Game Loop

关于这个 game loop,我们参考作者给的流程图,再结合自己的理解。

截图来自文中,版权作者所有

游戏主要流程在于:

  1. 初始化(Configure App, Window & Graphics),这个好理解,就是渲染游戏的最初状态和界面。
  2. Poll OS for Input State. 我理解的就是游戏都需要有一个触发机制去改变状态 (state)的,比如,一个事件、一个动作等等;
  3. Call Tick Function. 这个我的理解就好比每个游戏的游戏引擎,帮助我们把所有的事件、动作转化为游戏理解的逻辑,去修改状态,本文主要使用 bracket-lib 引擎。
  4. Update Screen. 状态的改变,就需要去实时更新界面了。

周而复始,就能把游戏运行起来了。

文中介绍了 bracket-lib 引擎。下面我们就来看看如何使用这个。

Bracket-Lib 使用

引入插件:

1
2
ini复制代码[dependencies]
bracket-lib="~0.8.1"

关于插件的版本号说明,可以看第一章介绍的:

=0.8.1 只能使用该版本;

^0.8.1 可以使用大于等于该版本,但是只能局限在0.x 版本,比如:1.x 就不能使用;

~0.8.1 则可以使用任何大于等于该版本,即使未来的一些版本有 bug。

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rust复制代码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 Terminal!");
}
}

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

let context = BTermBuilder::simple80x50()
.with_title("Flappy Dragon")
.build()?;

main_loop(context, State{})
}

先看运行效果:

简简单单的就可以在终端屏幕打印出文字:Hello, Bracket Terminal!

Creating Different Game Modes

我觉得这个更像是游戏的状态描述,在这个游戏 demo 中,需要有三个 modes:

  1. Menu: The player is waiting at the main menu.
  2. Playing: Game play is in progress.
  3. End: The game is over.

显然这个 modes 可以用一个 enum 表示:

1
2
3
4
5
vbnet复制代码enum GameMode {
Menu,
Playing,
End,
}

这就需要在我们定义的 State 加上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rust复制代码struct State {
mode: GameMode,
}

// 并 new 时初始化
impl State {
fn new() -> Self {
State {
mode: GameMode::Menu,
}
}
}

// main 函数同时修改
main_loop(context, State::new())

有了状态描述了,接下来就是需要通过不同的状态下,执行不同的动作,以达到游戏的效果:

1
2
3
4
5
6
7
8
9
10
11
rust复制代码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),
}
}
}

在这个 tick 函数里,“实时”监听所有的状态变化,执行不同的动作,如:在 Playing 状态下,执行 self.play(ctx) 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rust复制代码fn play(&mut self, ctx: &mut BTerm) {
// TODO: Fill in this stub later
ctx.cls();
ctx.print_centered(5, "Playing Flappy Dragon");
ctx.print_centered(8, "(E) Play End");
ctx.print_centered(9, "(Q) Quit Game");

if let Some(key) = ctx.key {
match key {
VirtualKeyCode::E => self.mode = GameMode::End,
VirtualKeyCode::Q => ctx.quitting = true,
_ => {}
}
}

// 直接状态改变
// self.mode = GameMode::End;
}

play 函数里,根据监听不同的按键,决定状态的改变,这就把游戏运转起来了。

执行效果如下:

完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
rust复制代码use bracket_lib::prelude::*;

enum GameMode {
Menu,
Playing,
End,
}

struct State {
mode: GameMode,
}

impl State {
fn new() -> Self {
State {
mode: GameMode::Menu,
}
}

fn restart(&mut self) {
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,
_ => {}
}
}
}

fn dead(&mut self, ctx: &mut BTerm) {
ctx.cls();
ctx.print_centered(5, "You are dead!");
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();
ctx.print_centered(5, "Playing Flappy Dragon");
ctx.print_centered(8, "(E) Play End");
ctx.print_centered(9, "(Q) Quit Game");

if let Some(key) = ctx.key {
match key {
VirtualKeyCode::E => self.mode = GameMode::End,
VirtualKeyCode::Q => ctx.quitting = true,
_ => {}
}
}

// 直接状态改变
// 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())
}

今天临时安排其他工作,跟着书中流程,顺出代码,通过 bracket-lib 引擎实时监听 State 的变化,执行不同的游戏动作,虽然简单的利用虚拟按键,响应状态的变化来模拟游戏的推进,但基本也就构成了游戏的逻辑。

明天继续第三章的学习!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%