0x09. 遊戲菜單

如今一啓動程序就很厲害了,無垠的宇宙有一艘飛船在飛行,可是一般遊戲不是這樣的呀,好歹給我個選項界面。要用上以前寫好的切換視圖的操做了。前端

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 ..., 慢慢填吧.

相關文章
相關標籤/搜索