17个稳定版本

使用旧的Rust 2015

2.0.5 2023年5月14日
2.0.4 2016年11月21日
1.3.3 2016年11月17日
1.2.3 2016年10月15日

#4#连续的


被用于 tilenet_ren

GPL-3.0 许可证

42KB
693

TileNet

此库提供了简单的基于瓦片的碰撞检测和自定义碰撞解决。它还提供了一些辅助方法,使其更容易使用。

文档

请运行 cargo doc --open 来查看文档。


lib.rs:

TileNet 保存整数对齐的瓦片,用于广泛相的连续碰撞检测。 TileNet 的目的是为有抱负的游戏程序员提供一个坚实的、基于瓦片、连续、简单的碰撞库。

工作原理

该库基于DDA Supercover算法,它是Bresenham算法的扩展。对于每个移动顶点,它创建一条线。每条线的重叠瓦片都会被报告。您的动态对象决定如何移动。它可以调整速度,并重试碰撞。它也可以接受并移动。

限制

该库在处理大坐标时可能会遇到问题。这是因为将一个小增量添加到2^24以上的浮点数可能根本无法注册。当接近2^24时,精度变得更差。技术原因是32位浮点数有24位在它的尾数部分。您不需要担心浮点误差,因为库通过检查端点来确保一致性。

示例 - 设置

我们从将tile net包含到我们的程序中并创建一个空网开始

extern crate tile_net;
use tile_net::*;
fn main() {
  let net: TileNet<usize> = TileNet::new(10, 10);
  println!["{:?}", net];
}

这创建了一个包含 usize 作为其元素的 TileNet。所有瓦片都初始化为 Defaultusize。您现在可以编辑各种瓦片

extern crate tile_net;
use tile_net::*;
fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set(&1, (9, 0));
  println!["{:?}", net];
}

有几个辅助函数,因此您可以轻松地绘制一些有趣的东西

extern crate tile_net;
use tile_net::*;
fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set_row(&1, 0);
  net.set_row(&1, 9);
  net.set_col(&1, 0);
  net.set_col(&1, 9);
  net.set_box(&1, (3, 3), (5, 7));
  println!["{:?}", net];
}

只要它具有以下特质,您就可以在 TileNet 中使用任何元素

extern crate tile_net;
use tile_net::*;
#[derive(Clone, Debug, Default)]
struct Example(i32);
fn main() {
  let mut net: TileNet<Example> = TileNet::new(10, 10);  // Requires Default trait
  net.set_row(&Example(1), 0);  // Requires Clone trait
  net.set_row(&Example(2), 9);
  net.set_col(&Example(3), 0);
  net.set_col(&Example(4), 9);
  net.set_box(&Example(5), (3, 3), (5, 7));
  println!["{:?}", net];  // Requires Debug trait
}

碰撞检测

TileNet 不用于将瓦片绘制到网格中,其主要重点是连续的、基于瓦片的顶点碰撞检测。连续碰撞检测(CCD)防止对象在帧中穿透其他对象。这发生在我们只检查对象移动的起点和终点时。此库在每个瓦片上插值。因此,每个中间瓦片都会被检查。让我们看看一个例子。

extern crate tile_net;
use tile_net::*;

fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set_row(&1, 0);
  net.set_row(&2, 9);
  net.set_col(&3, 0);
  net.set_col(&4, 9);
  net.set_box(&5, (3, 3), (5, 7));
  println!["{:?}", net];

  // We create a new object with speed (100, 100) and check where our collision points will be!
  let mut collider = MyObject::new();
  let supercover = collider.tiles();  // This is the supercover of the current movement
  // in the grid, it just returns integer points of every tile that collider touches
  let tiles = net.collide_set(supercover);
  if collider.resolve(tiles, &mut ()) {
    println!["Able to move"];
  } else {
    println!["Unable to move"];
  }
}

#[derive(Debug)]
struct MyObject {
  pts: Vec<(f32, f32)>,
  pos: Vector,
  mov: Vector,
}

impl MyObject {
  fn new() -> MyObject {
    MyObject {
      // These are the points in object-space
      pts: vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)],
      // The current position of the object
      pos: Vector(1.1, 1.1),
      // The movement vector
      mov: Vector(100.0, 100.0),
    }
  }

}

impl Collable<usize, ()> for MyObject {
  // This function returns the vertices of the object
  // The points are used by the collision engine to create a set of
  // lines from the beginning to the end of the frame.
  fn points<'a>(&'a self) -> Points<'a> {
    Points::new(self.pos, &self.pts)
  }

  // The physics engine uses this function in conjunction with points to compute
  // the lines - and thus - tiles it will iterate over during a collision test.
  fn queued(&self) -> Vector {
    self.mov
  }

  // Here is where your magic happens!
  // You will be given a TileSet, which contains all tiles which your object
  // collides between the current frame jump.
  // The tiles given are in nearest-order, so the first tiles you get from the
  // iterator are always the ones you will collide with first.
  fn resolve<'a, I>(&mut self, mut set: TileSet<'a, usize, I>, _state: &mut ()) -> bool
    where I: Iterator<Item = (i32, i32)>
  {
    if set.all(|x| *x == 0) {  // If there is no collision (we only collide with non-zero tiles)
      self.pos = self.pos + self.mov;
      self.mov = Vector(0.0, 0.0);
      true
    } else if self.mov.norm2sq() > 1e-6 {  // There was collision, but our speed isn't tiny
      self.mov.scale(0.9);
      false
    } else {  // This may happen if we generate a world where we're stuck in a tile,
              // normally this will never happen, this library can preserve consistency
              // perfectly.
      true
    }
  }
}

使用 resolve 可以通过循环来运行。在 resolve 中充分缩小移动向量后,你可能会得到一个不会引起碰撞的 TileSet。这就是我们几乎完美找到位置的方法。你可以在 resolve 中使用其他方法。任何适合你需求的方法都行。这里再次给出例子,但这次我们使用循环来解决碰撞

extern crate tile_net;
use tile_net::*;

fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set_row(&1, 0);
  net.set_row(&2, 9);
  net.set_col(&3, 0);
  net.set_col(&4, 9);
  net.set_box(&5, (3, 3), (5, 7));
  println!["{:?}", net];

  // Movement vector is (100, 100), which is way outside the box
  let mut collider = MyObject::new();
  loop {
    let supercover = collider.tiles();
    let tiles = net.collide_set(supercover);
    if collider.resolve(tiles, &mut ()) {
      println!["Able to move"];
      break;
    } else {
      println!["Unable to move"];
    }
  }
  // We are interested in the final position!
  println!["{:?}", collider];
}

#[derive(Debug)]
struct MyObject {
  pts: Vec<(f32, f32)>,
  pos: Vector,
  mov: Vector,
}

impl MyObject {
  fn new() -> MyObject {
    MyObject {
      pts: vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)],
      pos: Vector(1.1, 1.1),
      mov: Vector(100.0, 100.0),
    }
  }

}

impl Collable<usize, ()> for MyObject {
  // This function returns the vertices of the object
  // The points are used by the collision engine to create a set of
  // lines from the beginning to the end of the frame.
  fn points<'a>(&'a self) -> Points<'a> {
    Points::new(self.pos, &self.pts)
  }

  // The physics engine uses this function in conjunction with points to compute
  // the lines - and thus - tiles it will iterate over during a collision test.
  fn queued(&self) -> Vector {
    self.mov
  }

  // Here is where your magic happens!
  // You will be given a TileSet, which contains all tiles which your object
  // collides between the current frame jump.
  // The tiles given are in nearest-order, so the first tiles you get from the
  // iterator are always the ones you will collide with first.
  fn resolve<'a, I>(&mut self, mut set: TileSet<'a, usize, I>, _state: &mut ()) -> bool
    where I: Iterator<Item = (i32, i32)>
  {
    if set.all(|x| *x == 0) {  // If there is no collision (we only collide with non-zero tiles)
      self.pos = self.pos + self.mov;
      self.mov = Vector(0.0, 0.0);
      true  // Means we resolved correctly
    } else if self.mov.norm2sq() > 1e-6 {  // There was collision, but our speed isn't tiny
      self.mov.scale(0.9);
      false  // Means we did not resolve collision
    } else {
      true
    }
  }
}

你可以尝试使用更细致的方法,而不是缩小并再次检查。一种方法可能是检查第一个碰撞点并缩小到该距离。一切都是基于迭代器的。

TileView

为了绘图,你可能想要避免将巨大的网格发送到 GPU,所以我们使用网格的视图。

extern crate tile_net;
use tile_net::*;
fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set_row(&1, 0);
  net.set_row(&2, 9);
  net.set_col(&3, 0);
  net.set_col(&4, 9);
  net.set_box(&5, (3, 3), (5, 7));
  println!["{:?}", net];
  // This creates a box with x from 0 to 4 and y from 3 to 6
  // Note that the last elements are not included (so for x: 0, 1, 2, 3, but not 4)
  for element in net.view_box((0, 4, 3, 6)) {
    let (value, col, row) = element;
    // Draw here!
    println!["{}-{} = {}", row, col, value];
  }
  // This just prints every single element in the net
  for element in net.view_all() {
    let (value, col, row) = element;
    // Draw here!
    println!["{}-{} = {}", row, col, value];
  }
  // Looks from (0, 1) to (6, 5). This takes care of negative indices that may be created.
  // The first argument represents the center. The second argument is the span around that
  // center.
  for element in net.view_center((3, 3), (4, 2)) {
    let (value, col, row) = element;
    // Draw here!
    println!["{}-{} = {}", row, col, value];
  }
  // Same as `view_center` but allows floats for the first pair.
  // Makes sure that the left-most bound will always be 0.
  for element in net.view_center_f32((3.0, 3.0), (4, 2)) {
    let (value, col, row) = element;
    // Draw here!
    println!["{}-{} = {}", row, col, value];
  }
}

人体工程学

而不是使用手动循环,你可以使用内置的 solve。它调用 presolve,围绕 resolve 运行循环,然后调用 postsolve,使用表示是否找到解决方案以及至少遇到了一个碰撞的布尔值。

extern crate tile_net;
use tile_net::*;

fn main() {
  let mut net: TileNet<usize> = TileNet::new(10, 10);
  net.set_row(&1, 0);
  net.set_row(&2, 9);
  net.set_col(&3, 0);
  net.set_col(&4, 9);
  net.set_box(&5, (3, 3), (5, 7));
  println!["{:?}", net];

  let mut collider = MyObject::new();
  collider.solve(&net, &mut ());  // Much simpler than the loop!
  println!["{:?}", collider];
}

#[derive(Debug)]
struct MyObject {
  pts: Vec<(f32, f32)>,
  pos: Vector,
  mov: Vector,
}

impl MyObject {
  fn new() -> MyObject {
    MyObject {
      pts: vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)],
      pos: Vector(1.1, 1.1),
      mov: Vector(100.0, 100.0),
    }
  }

}

impl Collable<usize, ()> for MyObject {
  fn points<'a>(&'a self) -> Points<'a> {
    Points::new(self.pos, &self.pts)
  }

  fn queued(&self) -> Vector {
    self.mov
  }

  fn postsolve(&mut self, _collided_once: bool, resolved: bool, _state: &mut ()) {
    if resolved {
      println!["Able to move"];
    } else {
      println!["Unable to move"];
    }
  }

  fn resolve<'a, I>(&mut self, mut set: TileSet<'a, usize, I>, _state: &mut ()) -> bool
    where I: Iterator<Item = (i32, i32)>
  {
    if set.all(|x| *x == 0) {  // If there is no collision (we only collide with non-zero tiles)
      self.pos = self.pos + self.mov;
      self.mov = Vector(0.0, 0.0);
      true  // Means we resolved correctly
    } else if self.mov.norm2sq() > 1e-6 {  // There was collision, but our speed isn't tiny
      self.mov.scale(0.9);
      false  // Means we did not resolve collision
    } else {
      true
    }
  }
}

状态

你可能已经在 presolvesolvepostsolve 中看到了 state 变量。你可以在该变量中放置任意信息。这允许你在三个阶段之间以及 solve 调用之外进行通信。

状态适用于存在不应该是你正在实现的 Collable 部分属性的情况。除了让你的 Collable 更加干净外,你还可以避免在对象中存储冗余信息。

请查看示例目录,其中我们使用 presolve 和 postsolve 来判断我们的对象是否能跳起。

依赖项