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
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
。所有瓦片都初始化为 Default
的 usize
。您现在可以编辑各种瓦片
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
}
}
}
状态
你可能已经在 presolve
、solve
和 postsolve
中看到了 state
变量。你可以在该变量中放置任意信息。这允许你在三个阶段之间以及 solve
调用之外进行通信。
状态适用于存在不应该是你正在实现的 Collable
部分属性的情况。除了让你的 Collable
更加干净外,你还可以避免在对象中存储冗余信息。
请查看示例目录,其中我们使用 presolve 和 postsolve 来判断我们的对象是否能跳起。