0x06. 窗口上的矩形

592 阅读3分钟

放个矩形

现在要在窗口上放一个矩形, 顺便学习一下 Rust 的其他语法.
我们在 views 模块内写关于 rect 的结构体, 有一个不幸的消息, 之前写的 ViewA ViewB 目前是用不上了, 之后视图切换之类的操作肯定有用的, 现在我们先把它们处理掉.

来定义一个矩形的结构体, 一个矩形在窗口上有平面直角坐标系的 X 轴和 Y 轴的坐标和还有矩形本身的长宽.

如果想自己写矩形的渲染, 可以通过 OpenGL 来绘制两个三角形来组成一个矩形, 而且如果还要做矩形的移动等操作, 还要用线性代数的矩阵来做处理, 幸好 SDL 提供了相关的 API 让我们可以方便地绘制矩形

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Rectangle {
    pub x: f64,
    pub y: f64,
    pub w: f64,
    pub h: f64,
}

impl Rectangle {
    pub fn to_sdl(&self) -> Option<SdlRect> {
        assert!(self.w >= 0.0 && self.h >= 0.0);
        Some(SdlRect::new(self.x as i32,
                          self.y as i32,
                          self.w as u32,
                          self.h as u32))
    }
}

关于 #[derive(Clone, Copy, Debug, PartialEq)] 这块, 一般的结构体通过 let 绑定到变量会发生所有权的 move 行为, 加了这 Clone Copy属性之后, 这个结构体进行变量绑定时就变得跟整型浮点型之类的类型一样的 copy 行为, 举例说明

let a: i32 = 10;
let b = a; // copy, a 还能被使用

struct Test {}
impl Test {
    pub fn new() {
        ...
    }
}
let c = Test::new();
let d = c; // move, c 所有权转移了

关于 Debug 属性就是使结构体可以让 println! 这些宏打印, PartialEq 就是让结构体可比较.

来定义个 RectView 结构体来实现一下 View trait

struct Rect {
    rect: Rectangle,
}

pub struct RectView {
    player: Rect,
}

impl RectView {
    pub fn new() -> Self {
        Self {
            player: Rect {
                rect: Rectangle {
                    x: 64.0,
                    y: 64.0,
                    w: 32.0,
                    h: 32.0,
                }
            }
        }
    }
}
impl View for RectView {
    fn render(&mut self, context: &mut Phi) -> ViewAction {
        let canvas = &mut context.canvas;
        let events = &mut context.events;
        if events.now.quit || events.now.key_escape == Some(true) {
            return ViewAction::Quit;
        }
        canvas.set_draw_color(Color::RGB(0, 50, 0));
        canvas.clear();
        canvas.set_draw_color(Color::RGB(200, 200, 50));
        canvas.fill_rect(self.player.rect.to_sdl().unwrap())
            .expect("fill rect fail");
        ViewAction::None
    }
}

定义好这些东西之后, 再在 spawn 函数内把之前的 ViewA 改成 RectView, 此外上面的代码值得注意的是要先 clear() 背景色, 再填充 rect 颜色

...
let mut current_view: Box<View> = box views::RectView::new();
...

现在运行一下, 矩形已经渲染出来了.

通过方向键控制矩形移动

我们发觉到, Rectangle 存储了矩形的宽高还有位置, 我们现在只要修改这个结构体的值就可以改变矩形的宽高跟位置, 通过键盘的方向键来重新计算 x, y 的值就可以达到让矩形移动的效果.

按照惯例改一下事件宏的调用传入

events_macro! {
    keyboard: {
        key_escape: Escape,
        key_up: Up,
        key_down: Down,
        key_left: Left,
        key_right: Right,
        key_space: Space
    },
    else: {
        quit: Quit { .. }
    }
}

然后在 render 函数中写重新计算值的逻辑, 现在方向键就四个, 然后我们要来考虑所有的情况, 我之前的想法是一个一个用 if 去判断按键的触发, 但是明显这个方式写出来的代码太 ugly 了, 幸好 Rust 模式匹配非常棒. 先来列一下会有的东西, 上下左右跟斜角移动, 这里定义个常量来作为移动的速度

const PLAYER_SPEED: f64 = 0.35;

impl View for RectView {
    fn render(&mut self, context: &mut Phi) -> ViewAction {
        let canvas = &mut context.canvas;
        let events = &mut context.events;

        if events.now.quit || events.now.key_escape == Some(true) {
            return ViewAction::Quit;
        }

        let diagonal: bool = (events.key_up ^ events.key_down) &&
            (events.key_left ^ events.key_right);
        let moved = if diagonal { 1.0 / 2.0f64.sqrt() } else { 1.0 } *
            PLAYER_SPEED;
        let dx = match (events.key_left, events.key_right) {
            (true, true) | (false, false) => 0.0,
            (true, false) => -moved,
            (false, true) => moved,
        };

        let dy = match (events.key_up, events.key_down) {
            (true, true) | (false, false) => 0.0,
            (true, false) => -moved,
            (false, true) => moved,
        };

        self.player.rect.x += dx;
        self.player.rect.y += dy;

        canvas.set_draw_color(Color::RGB(0, 50, 0));
        canvas.clear();

        canvas.set_draw_color(Color::RGB(200, 200, 50));
        canvas.fill_rect(self.player.rect.to_sdl().unwrap())
            .expect("fill rect fail");

        ViewAction::None
    }
}

至此已经可以在窗口上显示了矩形, 而且还能根据键盘方向键控制矩形移动, 了解到了 Rust 模式匹配的灵活运用, 结构体 copy 行为的定义, 就这样吧, 之后再处理其他的事体.