libGDX- 精确碰撞检测 - 多边形创建?
libGDX- Exact collision detection - Polygon creation?
我有一个关于 libGDX 碰撞检测的问题。因为这是一个相当具体的问题,我还没有在互联网上找到任何好的解决方案。
所以,我已经创建了 "humans",它由不同的 body 部分组成,每个部分都有 rectangle-shaped 碰撞检测。
现在我想实现武器和技能,例如看起来像这样:
问题
当有这样的技能时,在碰撞检测中使用矩形对玩家来说真的很令人沮丧:他们可以成功躲避技能,但碰撞检测器仍然会伤害他们。
方法一:
在我开始使用 Libgdx 之前,我已经创建了一个 Android 具有自定义引擎和类似技能的游戏。我通过以下方式解决了问题:
- 检测矩形碰撞
- 计算重叠矩形截面
- 检查技能重叠部分的每个像素的透明度
- 如果找到任何 non-transparent 像素 -> 碰撞
这是一种繁重的方法,但由于只检查重叠像素,而游戏的其余部分非常轻,因此完全可以正常工作。
目前我的技能图像加载为 "TextureRegion",无法访问单个像素。
我发现 libGDX 有一个 Pixmap class,它允许进行这样的像素检查。问题是:另外将它们加载为 Pixmaps 会 1. 更重并且 2. 破坏纹理系统的全部目的。
另一种方法是仅将所有技能加载为像素图。你怎么看:这是一个好方法吗?是否可以在屏幕上绘制许多像素图而不会出现任何问题和延迟?
方法二:
另一种方法是创建具有技能形状的多边形并将它们用于碰撞检测。
a)
但是我如何为每一项技能定义多边形形状(其中有超过 150 种)?好吧,浏览了一段时间后,我发现了这个有用的工具:http://www.aurelienribon.com/blog/projects/physics-body-editor/
它允许手动创建多边形形状,然后将它们保存为 JSON 文件,可由 libGDX 应用程序读取。现在困难来了:
- 物理 Body 编辑器连接到 Box2d(我没有使用)。我将不得不添加整个 Box2d 物理引擎(我根本不需要)只是因为一个微小的碰撞检测,或者我将不得不编写一个自定义的 BodyEditorLoader,这将是一个艰难、复杂且 time-intensive任务
- 同一个技能精灵的一些图像在形状上有很大的不同(比如第二个技能精灵的例子)。使用 Body 编辑器工具时,我不仅需要定义每个技能的形状,而且还需要定义每个技能的多个图像(最多 12 个)的形状。在实施这几十种多边形形状时,那将是极其 time-intensive 和一团糟
b)
如果有任何流畅的方法可以自动从图像中生成多边形,那可能就是解决方案。我可以简单地将每个 sprite 部分连接到生成的多边形并以这种方式检查碰撞。但是有一些问题:
- 是否有任何平滑的工具可以从图像中生成多边形形状(并且不需要太多时间)?
- 我不认为像这样的工具(如果存在的话)可以直接处理纹理。它可能需要像素图。不过,在多边形创建后不需要保持像素图加载。任务依然极其繁重!
我目前的想法
我被困在这一点上,因为有几种可能的方法,但它们都有各自的困难。在我选择一条路继续编码之前,如果你能留下一些你的想法和知识就好了。
libGDX 中包含的 classes 和代码可能会在几秒钟内解决我的问题 - 因为我在 libGDX 真的很新,我只是不太了解关于它。
目前我认为我会采用方法 1:使用像素检测。这样我就可以在我以前的 Android 游戏中进行精确的碰撞检测。
你怎么看?
您好
菲利克斯
我使用了您引用的 body 编辑器,它能够为您生成多边形 and/or 圆。我还使用 Jackson 库为生成的 JSON 创建了一个加载器。这可能不是您的答案,因为您必须实施 box2d。但无论如何我都是这样做的。
/**
* Adds all the fixtures defined in jsonPath with the name'lookupName', and
* attach them to the 'body' with the properties defined in 'fixtureDef'.
* Then converts to the proper scale with 'width'.
*
* @param body the body to attach fixtures to
* @param fixtureDef the fixture's properties
* @param jsonPath the path to the collision shapes definition file
* @param lookupName the name to find in jsonPath json file
* @param width the width of the sprite, used to scale fixtures and find origin.
* @param height the height of the sprite, used to find origin.
*/
public void addFixtures(Body body, FixtureDef fixtureDef, String jsonPath, String lookupName, float width, float height) {
JsonNode collisionShapes = null;
try {
collisionShapes = json.readTree(Gdx.files.internal(jsonPath).readString());
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (JsonNode node : collisionShapes.findPath("rigidBodies")) {
if (node.path("name").asText().equals(lookupName)) {
Array<PolygonShape> polyShapes = new Array<PolygonShape>();
Array<CircleShape> circleShapes = new Array<CircleShape>();
for (JsonNode polygon : node.findPath("polygons")) {
Array<Vector2> vertices = new Array<Vector2>(Vector2.class);
for (JsonNode vector : polygon) {
vertices.add(new Vector2(
(float)vector.path("x").asDouble() * width,
(float)vector.path("y").asDouble() * width)
.sub(width/2, height/2));
}
polyShapes.add(new PolygonShape());
polyShapes.peek().set(vertices.toArray());
}
for (final JsonNode circle : node.findPath("circles")) {
circleShapes.add(new CircleShape());
circleShapes.peek().setPosition(new Vector2(
(float)circle.path("cx").asDouble() * width,
(float)circle.path("cy").asDouble() * width)
.sub(width/2, height/2));
circleShapes.peek().setRadius((float)circle.path("r").asDouble() * width);
}
for (PolygonShape shape : polyShapes) {
Vector2 vectors[] = new Vector2[shape.getVertexCount()];
for (int i = 0; i < shape.getVertexCount(); i++) {
vectors[i] = new Vector2();
shape.getVertex(i, vectors[i]);
}
shape.set(vectors);
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
}
for (CircleShape shape : circleShapes) {
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
}
}
}
}
我会这样称呼它:
physics.addFixtures(body, fixtureDef, "ship/collision_shapes.json", shipType, width, height);
然后进行碰撞检测:
public ContactListener shipsExplode() {
ContactListener listener = new ContactListener() {
@Override
public void beginContact(Contact contact) {
Body bodyA = contact.getFixtureA().getBody();
Body bodyB = contact.getFixtureB().getBody();
for (Ship ship : ships) {
if (ship.body == bodyA) {
ship.setExplode();
}
if (ship.body == bodyB) {
ship.setExplode();
}
}
}
};
return listener;
}
然后你会添加监听器到世界:
world.setContactListener(physics.shipsExplode());
我的精灵的宽度和高度很小,因为一旦你开始使用 box2d,你处理的是米而不是像素。例如,一个精灵的高度为 0.8f,宽度为 1.2f。如果您以像素为单位制作精灵的宽度和高度,则物理引擎会达到 http://www.iforce2d.net/b2dtut/gotchas
中内置的速度限制
不知道这对你们是否仍然重要,但我构建了一个小 python 脚本,returns 图像边缘点的像素位置。剧本还有改进的余地,但对我来说,目前还可以...
from PIL import Image, ImageFilter
filename = "dship1"
image = Image.open(filename + ".png")
image = image.filter(ImageFilter.FIND_EDGES)
image.save(filename + "_edge.png")
cols = image.width
rows = image.height
points = []
w = 1
h = 1
i = 0
for pixel in list(image.getdata()):
if pixel[3] > 0:
points.append((w, h))
if i == cols:
w = 0
i = 0
h += 1
w += 1
i += 1
with open(filename + "_points.txt", "wb") as nf:
nf.write(',\n'.join('%s, %s' % x for x in points))
如果有更新,您可以在这里找到它们:export positions
就个人而言,我觉得像素到像素的碰撞对性能的影响太大了,并且提供了一些我仍然会感到受骗的实例 - (我被斧头的手柄击中了吗? )
如果是我,我会在每个技能上加一个"Hitbox"。 StreetFighter 是一款使用这种技术的流行游戏。 (较新的版本是 3D,但 Hitbox 碰撞仍然是 2D)Hitbox 可以随着动画逐帧变化。
此处留空以添加示例图片 - google "Streetfighter hitbox" 同时
对于您的斧头,可能沿着一端或两端的边缘有一个定义的矩形碰撞框 - 甚至在斧头的整个金属头上。
这使它相当简单,不必弄乱精确的多边形,但也不会像让每个像素都是自己的碰撞盒那样过于依赖性能。
我有一个关于 libGDX 碰撞检测的问题。因为这是一个相当具体的问题,我还没有在互联网上找到任何好的解决方案。
所以,我已经创建了 "humans",它由不同的 body 部分组成,每个部分都有 rectangle-shaped 碰撞检测。
现在我想实现武器和技能,例如看起来像这样:
问题
当有这样的技能时,在碰撞检测中使用矩形对玩家来说真的很令人沮丧:他们可以成功躲避技能,但碰撞检测器仍然会伤害他们。
方法一:
在我开始使用 Libgdx 之前,我已经创建了一个 Android 具有自定义引擎和类似技能的游戏。我通过以下方式解决了问题:
- 检测矩形碰撞
- 计算重叠矩形截面
- 检查技能重叠部分的每个像素的透明度
- 如果找到任何 non-transparent 像素 -> 碰撞
这是一种繁重的方法,但由于只检查重叠像素,而游戏的其余部分非常轻,因此完全可以正常工作。
目前我的技能图像加载为 "TextureRegion",无法访问单个像素。 我发现 libGDX 有一个 Pixmap class,它允许进行这样的像素检查。问题是:另外将它们加载为 Pixmaps 会 1. 更重并且 2. 破坏纹理系统的全部目的。
另一种方法是仅将所有技能加载为像素图。你怎么看:这是一个好方法吗?是否可以在屏幕上绘制许多像素图而不会出现任何问题和延迟?
方法二:
另一种方法是创建具有技能形状的多边形并将它们用于碰撞检测。
a) 但是我如何为每一项技能定义多边形形状(其中有超过 150 种)?好吧,浏览了一段时间后,我发现了这个有用的工具:http://www.aurelienribon.com/blog/projects/physics-body-editor/ 它允许手动创建多边形形状,然后将它们保存为 JSON 文件,可由 libGDX 应用程序读取。现在困难来了:
- 物理 Body 编辑器连接到 Box2d(我没有使用)。我将不得不添加整个 Box2d 物理引擎(我根本不需要)只是因为一个微小的碰撞检测,或者我将不得不编写一个自定义的 BodyEditorLoader,这将是一个艰难、复杂且 time-intensive任务
- 同一个技能精灵的一些图像在形状上有很大的不同(比如第二个技能精灵的例子)。使用 Body 编辑器工具时,我不仅需要定义每个技能的形状,而且还需要定义每个技能的多个图像(最多 12 个)的形状。在实施这几十种多边形形状时,那将是极其 time-intensive 和一团糟
b) 如果有任何流畅的方法可以自动从图像中生成多边形,那可能就是解决方案。我可以简单地将每个 sprite 部分连接到生成的多边形并以这种方式检查碰撞。但是有一些问题:
- 是否有任何平滑的工具可以从图像中生成多边形形状(并且不需要太多时间)?
- 我不认为像这样的工具(如果存在的话)可以直接处理纹理。它可能需要像素图。不过,在多边形创建后不需要保持像素图加载。任务依然极其繁重!
我目前的想法
我被困在这一点上,因为有几种可能的方法,但它们都有各自的困难。在我选择一条路继续编码之前,如果你能留下一些你的想法和知识就好了。
libGDX 中包含的 classes 和代码可能会在几秒钟内解决我的问题 - 因为我在 libGDX 真的很新,我只是不太了解关于它。
目前我认为我会采用方法 1:使用像素检测。这样我就可以在我以前的 Android 游戏中进行精确的碰撞检测。
你怎么看?
您好 菲利克斯
我使用了您引用的 body 编辑器,它能够为您生成多边形 and/or 圆。我还使用 Jackson 库为生成的 JSON 创建了一个加载器。这可能不是您的答案,因为您必须实施 box2d。但无论如何我都是这样做的。
/**
* Adds all the fixtures defined in jsonPath with the name'lookupName', and
* attach them to the 'body' with the properties defined in 'fixtureDef'.
* Then converts to the proper scale with 'width'.
*
* @param body the body to attach fixtures to
* @param fixtureDef the fixture's properties
* @param jsonPath the path to the collision shapes definition file
* @param lookupName the name to find in jsonPath json file
* @param width the width of the sprite, used to scale fixtures and find origin.
* @param height the height of the sprite, used to find origin.
*/
public void addFixtures(Body body, FixtureDef fixtureDef, String jsonPath, String lookupName, float width, float height) {
JsonNode collisionShapes = null;
try {
collisionShapes = json.readTree(Gdx.files.internal(jsonPath).readString());
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (JsonNode node : collisionShapes.findPath("rigidBodies")) {
if (node.path("name").asText().equals(lookupName)) {
Array<PolygonShape> polyShapes = new Array<PolygonShape>();
Array<CircleShape> circleShapes = new Array<CircleShape>();
for (JsonNode polygon : node.findPath("polygons")) {
Array<Vector2> vertices = new Array<Vector2>(Vector2.class);
for (JsonNode vector : polygon) {
vertices.add(new Vector2(
(float)vector.path("x").asDouble() * width,
(float)vector.path("y").asDouble() * width)
.sub(width/2, height/2));
}
polyShapes.add(new PolygonShape());
polyShapes.peek().set(vertices.toArray());
}
for (final JsonNode circle : node.findPath("circles")) {
circleShapes.add(new CircleShape());
circleShapes.peek().setPosition(new Vector2(
(float)circle.path("cx").asDouble() * width,
(float)circle.path("cy").asDouble() * width)
.sub(width/2, height/2));
circleShapes.peek().setRadius((float)circle.path("r").asDouble() * width);
}
for (PolygonShape shape : polyShapes) {
Vector2 vectors[] = new Vector2[shape.getVertexCount()];
for (int i = 0; i < shape.getVertexCount(); i++) {
vectors[i] = new Vector2();
shape.getVertex(i, vectors[i]);
}
shape.set(vectors);
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
}
for (CircleShape shape : circleShapes) {
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
}
}
}
}
我会这样称呼它:
physics.addFixtures(body, fixtureDef, "ship/collision_shapes.json", shipType, width, height);
然后进行碰撞检测:
public ContactListener shipsExplode() {
ContactListener listener = new ContactListener() {
@Override
public void beginContact(Contact contact) {
Body bodyA = contact.getFixtureA().getBody();
Body bodyB = contact.getFixtureB().getBody();
for (Ship ship : ships) {
if (ship.body == bodyA) {
ship.setExplode();
}
if (ship.body == bodyB) {
ship.setExplode();
}
}
}
};
return listener;
}
然后你会添加监听器到世界:
world.setContactListener(physics.shipsExplode());
我的精灵的宽度和高度很小,因为一旦你开始使用 box2d,你处理的是米而不是像素。例如,一个精灵的高度为 0.8f,宽度为 1.2f。如果您以像素为单位制作精灵的宽度和高度,则物理引擎会达到 http://www.iforce2d.net/b2dtut/gotchas
中内置的速度限制不知道这对你们是否仍然重要,但我构建了一个小 python 脚本,returns 图像边缘点的像素位置。剧本还有改进的余地,但对我来说,目前还可以...
from PIL import Image, ImageFilter
filename = "dship1"
image = Image.open(filename + ".png")
image = image.filter(ImageFilter.FIND_EDGES)
image.save(filename + "_edge.png")
cols = image.width
rows = image.height
points = []
w = 1
h = 1
i = 0
for pixel in list(image.getdata()):
if pixel[3] > 0:
points.append((w, h))
if i == cols:
w = 0
i = 0
h += 1
w += 1
i += 1
with open(filename + "_points.txt", "wb") as nf:
nf.write(',\n'.join('%s, %s' % x for x in points))
如果有更新,您可以在这里找到它们:export positions
就个人而言,我觉得像素到像素的碰撞对性能的影响太大了,并且提供了一些我仍然会感到受骗的实例 - (我被斧头的手柄击中了吗? )
如果是我,我会在每个技能上加一个"Hitbox"。 StreetFighter 是一款使用这种技术的流行游戏。 (较新的版本是 3D,但 Hitbox 碰撞仍然是 2D)Hitbox 可以随着动画逐帧变化。
此处留空以添加示例图片 - google "Streetfighter hitbox" 同时
对于您的斧头,可能沿着一端或两端的边缘有一个定义的矩形碰撞框 - 甚至在斧头的整个金属头上。
这使它相当简单,不必弄乱精确的多边形,但也不会像让每个像素都是自己的碰撞盒那样过于依赖性能。