介绍

上一篇文章的延续。
这一次,我们将实现两个矩形的碰撞检测过程。
命中检测是导致诸如给予损坏或擦除对象等事件的条件。
逻辑在游戏编程中很重要。

矩形の当たり判定処理を実装する

矩形结构和命中检测过程

矩形是具有左上角坐标、宽度和高度的数据结构。

pub struct Rect {
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
}

判断两个矩形是否接触的函数如下。
判断条件可以说是“当x轴和y轴重叠时”。

impl Rect {
    ...
    pub fn is_hit(&self, rect: &Rect) -> bool {
        return (self.x <= rect.x + rect.width as i32 && rect.x <= self.x + self.width as i32)
            && (self.y <= rect.y + rect.height as i32 && rect.y <= self.y + rect.height as i32);
    }
}

两个矩形之间的位置关系大致可以分为以下四种模式。
看下图可以看到,只命中了④的情况。

(1) 完全不重叠

矩形の当たり判定処理を実装する

② 只有 x 轴重叠时

矩形の当たり判定処理を実装する

(3) 只有y轴重叠时

矩形の当たり判定処理を実装する

④ 两者重叠时

矩形の当たり判定処理を実装する

另外,如果仔细观察Rect.is_hit函数的判断条件,可以发现x轴和y轴的判断处理逻辑是一样的。
两者都确定它们是否与起点的坐标和长度重叠。
因此,将通用逻辑切割成一个单独的函数,并进行如下重构。

impl Rect {
    ...
    pub fn is_hit(&self, rect: &Rect) -> bool {
        return is_hit(self.x, self.width, rect.x, rect.width)
            && is_hit(self.y, self.height, rect.y, rect.height);
    }
}

fn is_hit(s1: i32, l1: u32, s2: i32, l2: u32) -> bool {
    let e1 = s1 + l1 as i32;
    let e2 = s2 + l2 as i32;
    return s1 <= e2 && s2 <= e1;
}

测试实施

然后实现测试代码。
确认线段碰撞检测is_hit函数和矩形碰撞检测Rect.is_hit函数正常工作。

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_line_hit() {
        assert_eq!(is_hit(50, 50, 19, 30), false);
        assert_eq!(is_hit(50, 50, 101, 20), false);
        assert_eq!(is_hit(50, 50, 40, 11), true);
        assert_eq!(is_hit(50, 50, 60, 15), true);
        assert_eq!(is_hit(50, 50, 90, 30), true);
    }

    #[test]
    fn test_rect_hit() {
        let rect = Rect::new(50, 50, 50, 50);
        assert_eq!(rect.is_hit(&Rect::new(0, 0, 30, 30)), false);
        assert_eq!(rect.is_hit(&Rect::new(0, 70, 40, 40)), false);
        assert_eq!(rect.is_hit(&Rect::new(40, 0, 20, 20)), false);
        assert_eq!(rect.is_hit(&Rect::new(40, 40, 15, 18)), true);
        assert_eq!(rect.is_hit(&Rect::new(60, 60, 10, 10)), true);
    }
}

cargo test 上运行测试。

> cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.06s
     Running unittests src\main.rs (target\debug\deps\sample_sdl2-0e5b02a7694a67db.exe)

running 2 tests
test rect::tests::test_line_hit ... ok
test rect::tests::test_rect_hit ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

好像没问题。
如果有复杂的流程,把共同的部分提取出来或者分成小流程进行测试
如果出现问题,将更容易确定问题位置。

确认

通过实际移动它来检查它。
为了视觉清晰,我们在两个矩形碰撞时改变颜色。

矩形の当たり判定処理を実装する

源代码

这是此实现的源代码。

重新CT。 rs
pub struct Rect {
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
}

impl Rect {
    pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
        return Rect {
            x: x,
            y: y,
            width: width,
            height: height,
        };
    }

    pub fn is_hit(&self, rect: &Rect) -> bool {
        return is_hit(self.x, self.width, rect.x, rect.width)
            && is_hit(self.y, self.height, rect.y, rect.height);
    }
}

fn is_hit(s1: i32, l1: u32, s2: i32, l2: u32) -> bool {
    let e1 = s1 + l1 as i32;
    let e2 = s2 + l2 as i32;
    return s1 <= e2 && s2 <= e1;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_line_hit() {
        assert_eq!(is_hit(50, 50, 19, 30), false);
        assert_eq!(is_hit(50, 50, 101, 20), false);
        assert_eq!(is_hit(50, 50, 40, 11), true);
        assert_eq!(is_hit(50, 50, 60, 15), true);
        assert_eq!(is_hit(50, 50, 90, 30), true);
    }

    #[test]
    fn test_rect_hit() {
        let rect = Rect::new(50, 50, 50, 50);
        assert_eq!(rect.is_hit(&Rect::new(0, 0, 30, 30)), false);
        assert_eq!(rect.is_hit(&Rect::new(0, 70, 40, 40)), false);
        assert_eq!(rect.is_hit(&Rect::new(40, 0, 20, 20)), false);
        assert_eq!(rect.is_hit(&Rect::new(40, 40, 15, 18)), true);
        assert_eq!(rect.is_hit(&Rect::new(60, 60, 10, 10)), true);
    }
}
矿。 rs
use sdl2::event::Event;
use sdl2::keyboard::Scancode;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
use std::time::Duration;

mod rect;

fn draw_rect(canvas: &mut Canvas<Window>, rect: &rect::Rect, color: Color) {
    canvas.set_draw_color(color);
    // 画面描画用のRectを生成して渡す
    canvas.fill_rect(Rect::new(rect.x, rect.y, rect.width, rect.height));
}

pub fn main() {
    let sdl_context = sdl2::init().unwrap();
    let video_subsystem = sdl_context.video().unwrap();
    let window = video_subsystem
        .window("Rust-SDL2", 800, 600)
        .position_centered()
        .build()
        .unwrap();
    let mut rect = rect::Rect::new(50, 100, 100, 100);
    let rect2 = rect::Rect::new(350, 250, 100, 100);
    let white = Color::RGB(255, 255, 255);
    let green = Color::RGB(0, 255, 0);
    let mut canvas = window.into_canvas().build().unwrap();
    let mut event_pump = sdl_context.event_pump().unwrap();
    'running: loop {
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. } => break 'running,
                _ => {}
            }
        }
        let state = event_pump.keyboard_state();
        if state.is_scancode_pressed(Scancode::Up) {
            rect.y -= 5;
        }
        if state.is_scancode_pressed(Scancode::Down) {
            rect.y += 5;
        }
        if state.is_scancode_pressed(Scancode::Left) {
            rect.x -= 5;
        }
        if state.is_scancode_pressed(Scancode::Right) {
            rect.x += 5;
        }
        canvas.set_draw_color(Color::RGB(0, 0, 0));
        canvas.clear();

        let is_hit = rect.is_hit(&rect2);
        draw_rect(&mut canvas, &rect2, if is_hit { green } else { white });
        draw_rect(&mut canvas, &rect, if is_hit { green } else { white });

        canvas.present();
        std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }
}

参考


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308628229.html

相关文章:

  • 2021-09-02
  • 2021-08-23
  • 2022-01-18
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-01-09
猜你喜欢
  • 2021-09-10
  • 2021-11-19
  • 2021-12-16
  • 2022-12-23
  • 2022-01-18
相关资源
相似解决方案