如今一啓動程序就很厲害了,無垠的宇宙有一艘飛船在飛行,可是一般遊戲不是這樣的呀,好歹給我個選項界面。要用上以前寫好的切換視圖的操做了。前端
pub enum ViewAction {
None,
Quit,
ChangeView(Box<dyn View>)
}
pub fn spawn(title: &str) {
// ...
let mut current_view: Box<dyn View> =
Box::new(views::menu::Menu::new(&mut context));
'running: loop {
// ...
match current_view.render(&mut context, elapsed) {
ViewAction::None => context.canvas.present(),
ViewAction::Quit => break 'running,
ViewAction::ChangeView(view) => current_view = view
}
}
}
pub struct Menu;
impl View for Menu {
fn render(&mut self, context: &mut Phi, _: f64) -> ViewAction {
let events = &context.events;
let canvas = &mut context.canvas;
if events.now.quit || events.now.key_escape == Some(true) {
return ViewAction::Quit;
}
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
ViewAction::None
}
}
複製代碼
如今只要一啓動,啓動的是菜單頁面,雖然如今菜單頁面什麼都沒有,一片黑。咱們還要把 Cargo.toml
改一下,由於菜單確定要用到文字。canvas
[dependencies.sdl2]
version = "0.29"
default-features = false
features = ["image", "ttf"]
複製代碼
而後咱們要將菜單頁的行爲抽象,一個選項具有功能,還具有標題函數
struct Action {
func: Box<Fn(&mut Phi) -> ViewAction>,
idle_sprite: Sprite,
hover_sprite: Sprite,
}
複製代碼
接着實現一下 Action
給這玩意加點料.oop
fn new(
context: &mut Phi,
label: &'static str,
func: Box<dyn Fn(&mut Phi) -> ViewAction>,
) -> Self {
Action {
func,
idle_sprite: context
.ttf_str_sprite(
label,
"assets/belligerent.ttf",
32,
Color::RGB(220, 220, 220),
)
.expect("Failed to load idle sprite font"),
hover_sprite: context
.ttf_str_sprite(
label,
"assets/belligerent.ttf",
38,
Color::RGB(255, 255, 255),
)
.expect("Failed to load hover sprite font"),
}
}
複製代碼
這裏放了兩個字體, 字體文件能夠克隆教程項目拿去. 可是如今咱們緊要任務是得學一下怎麼渲染文本, 順道實現一下 ttf_str_sprite
函數字體
pub struct Phi<'window> {
pub events: Events,
pub canvas: Renderer<'window>,
pub ttf_context: &'window Sdl2TtfContext
}
impl<'window> Phi <'window> {
fn new(events: Events,
canvas: Renderer<'window>,
ttf_context: &'window Sdl2TtfContext,
) -> Phi<'window> {
Phi { events, canvas, ttf_context }
}
pub fn ttf_str_sprite(
&mut self,
text: &str,
font_path: &'static str,
size: u16,
color: Color,
) -> Option<Sprite> {
self.ttf_context
.load_font(Path::new(font_path), size)
.ok()
.and_then(|font| font
.render(text).blended(color).ok()
.and_then(|surface| self.canvas
.create_texture_from_surface(&surface).ok()
.map(Sprite::new)
)
}
}
複製代碼
加載字體完成, 而後能夠優化一下, 其實也不算優化, 就是 cache font. 值得一提的就是, map 會返回 enum Option
值, 因此不須要再本身包裝 Some
之類的.flex
pub struct Phi<'window> {
...
cached_fonts:
HashMap<(&'static str, u16), sdl2::ttf::Font<'window, 'window>>,
}
pub fn ttf_str_sprite(
&mut self,
text: &str,
font_path: &'static str,
size: u16,
color: Color,
) -> Option<Sprite> {
if let Some(font) = self.cached_fonts.get(&(font_path, size)) {
return font
.render(text)
.blended(color)
.ok()
.and_then(|surface| {
self.canvas.create_texture_from_surface(&surface).ok()
})
.map(Sprite::new);
}
self.ttf_context
.load_font(Path::new(font_path), size)
.ok()
.and_then(|font| {
self.cached_fonts.insert((font_path, size), font);
self.ttf_str_sprite(text, font_path, size, color)
})
}
複製代碼
如今這樣就很棒了, 下一趟加載字體, 會從 HashMap
裏取出來. 接着渲染一下選項. 還要計算讓文本居中, 這裏得本身手動計算, 忽然想起寫前端樣式
display: flex; justify-content: center; align-items: center;
居中三連. 還要處理選中狀態, 因此 Menu
結構加一個選中狀態值, 同時還要處理上下選擇鍵, 定義空格鍵調用函數.優化
pub struct Menu {
actions: Vec<Action>,
selected: i8,
}
impl Menu {
pub fn new(context: &mut Phi) -> Menu {
use crate::views::game::ShipView;
Self {
actions: vec![
Action::new(
context,
"New Game",
Box::new(|phi| {
ViewAction::ChangeView(Box::new(
ShipView::new(phi),
))
}),
),
Action::new(context, "Quit", Box::new(|_| ViewAction::Quit)),
]
}
}
}
impl View for Menu {
fn render(&mut self, context: &mut Phi, elapsed: f64) -> ViewAction {
let events = &context.events;
if events.now.quit || events.now.key_escape == Some(true) {
return ViewAction::Quit;
}
if events.now.key_up == Some(true) {
self.selected -= 1;
if self.selected < 0 {
self.selected = self.actions.len() as i8 - 1;
}
}
if events.now.key_space == Some(true) {
return (self.actions[self.selected as usize].func)(context);
}
if events.now.key_down == Some(true) {
self.selected += 1;
if self.selected >= self.actions.len() as i8 {
self.selected = 0;
}
}
let (win_w, win_h) = context.output_size();
let canvas = &mut context.canvas;
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
let label_h = 50.0;
let border_width: f64 = 3.0;
let box_w = 360.0;
let box_h = self.actions.len() as f64 * label_h;
let margin_h = 10.0;
self.actions.iter().enumerate().for_each(|(i, action)| {
if self.selected as usize == i {
let (w, h) = action.hover_sprite.size();
canvas.copy_sprite(
&action.hover_sprite,
Rectangle {
x: (win_w - w) / 2.0,
y: (win_h - box_h + label_h - h) / 2.0
+ label_h * i as f64,
w,
h,
},
);
} else {
let (w, h) = action.idle_sprite.size();
canvas.copy_sprite(
&action.idle_sprite,
Rectangle {
x: (win_w - w) / 2.0,
y: (win_h - box_h + label_h - h) / 2.0
+ label_h * i as f64,
w,
h,
},
);
}
});
ViewAction::None
}
}
複製代碼
給菜單頁加背景, 這個比較簡單, 使用上一節的 Background
結構, 同時 Menu
結構添加這些屬性ui
pub struct Menu {
...
bg_back: Background,
bg_middle: Background,
bg_front: Background,
}
複製代碼
天然地, Menu
的實現也要改一下spa
impl Menu {
pub fn new(context: &mut Phi) -> Self {
use crate::views::game::ShipView;
Self {
...
selected: 0,
bg_front: Background {
pos: 0.0,
vel: 80.0,
sprite: Sprite::load(&mut context.canvas, "assets/star_fg.png")
.unwrap(),
},
bg_middle: Background {
pos: 0.0,
vel: 40.0,
sprite: Sprite::load(&mut context.canvas, "assets/star_mg.png")
.unwrap(),
},
bg_back: Background {
pos: 0.0,
vel: 20.0,
sprite: Sprite::load(&mut context.canvas, "assets/star_bg.png")
.unwrap(),
},
}
}
}
複製代碼
接下來直接在 Menu
的 trait View render
函數裏渲染背景code
impl View for Menu {
fn render(&mut self, context: &mut Phi, elapsed: f64) -> ViewAction {
...
canvas.clear();
self.bg_back.render(canvas, elapsed);
self.bg_middle.render(canvas, elapsed);
self.bg_front.render(canvas, elapsed);
let label_h = 50.0;
...
}
}
複製代碼
大概就是這樣了, 其餘的邏輯照舊, 視圖切換之類的. 可能有問題沒覆蓋到, 直接看目錄那節給的倉庫源碼, 或者評論問我. 鴿了近五個月, 隨緣更新. 坑挖得太多了 Rust React-Native ..., 慢慢填吧.