libGDX- 精确碰撞检测 - 多边形创建?

libGDX- Exact collision detection - Polygon creation?

我有一个关于 libGDX 碰撞检测的问题。因为这是一个相当具体的问题,我还没有在互联网上找到任何好的解决方案。

所以,我已经创建了 "humans",它由不同的 body 部分组成,每个部分都有 rectangle-shaped 碰撞检测。

现在我想实现武器和技能,例如看起来像这样:

Skill example image

问题

当有这样的技能时,在碰撞检测中使用矩形对玩家来说真的很令人沮丧:他们可以成功躲避技能,但碰撞检测器仍然会伤害他们。

方法一:

在我开始使用 Libgdx 之前,我已经创建了一个 Android 具有自定义引擎和类似技能的游戏。我通过以下方式解决了问题:

  1. 检测矩形碰撞
  2. 计算重叠矩形截面
  3. 检查技能重叠部分的每个像素的透明度
  4. 如果找到任何 non-transparent 像素 -> 碰撞

这是一种繁重的方法,但由于只检查重叠像素,而游戏的其余部分非常轻,因此完全可以正常工作。

目前我的技能图像加载为 "TextureRegion",无法访问单个像素。 我发现 libGDX 有一个 Pixmap class,它允许进行这样的像素检查。问题是:另外将它们加载为 Pixmaps 会 1. 更重并且 2. 破坏纹理系统的全部目的。

另一种方法是仅将所有技能加载为像素图。你怎么看:这是一个好方法吗?是否可以在屏幕上绘制许多像素图而不会出现任何问题和延迟?

方法二:

另一种方法是创建具有技能形状的多边形并将它们用于碰撞检测。

a) 但是我如何为每一项技能定义多边形形状(其中有超过 150 种)?好吧,浏览了一段时间后,我发现了这个有用的工具:http://www.aurelienribon.com/blog/projects/physics-body-editor/ 它允许手动创建多边形形状,然后将它们保存为 JSON 文件,可由 libGDX 应用程序读取。现在困难来了:

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" 同时

对于您的斧头,可能沿着一端或两端的边缘有一个定义的矩形碰撞框 - 甚至在斧头的整个金属头上。

这使它相当简单,不必弄乱精确的多边形,但也不会像让每个像素都是自己的碰撞盒那样过于依赖性能。