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

3
我有一个关于libGDX碰撞检测的问题。因为这是一个相当具体的问题,我在互联网上没有找到任何好的解决方案。
所以,我已经创建了由不同身体部位组成的“人类”,每个部位都有矩形形状的碰撞检测。
现在我想要实现武器和技能,例如: 技能示例图片 问题:
使用矩形进行碰撞检测将会让玩家非常沮丧,例如出现这种技能:他们成功躲避了技能,但碰撞检测仍然会对他们造成伤害。
方法1:
在开始使用Libgdx之前,我使用自定义引擎创建了一款Android游戏,并使用了类似的技能。我通过以下方式解决了这个问题:
1. 检测矩形碰撞 2. 计算重叠的矩形部分 3. 检查技能重叠部分的每个像素是否透明 4. 如果发现任何不透明像素-> 碰撞
虽然这是一种比较繁琐的方法,但由于只检查重叠像素并且游戏的其余部分非常简单,所以它完全正常工作。
目前我的技能图像是以"TextureRegion"的形式加载,无法访问单个像素。我已发现libGDX有一个Pixmap类,可以允许进行像素检查。问题是:如果额外以Pixmap方式加载它们,将会更加沉重并且也违背了Texture系统的初衷。
另一种选择是只使用Pixmap来加载所有技能。你认为这是一种好的方式吗?在屏幕上绘制许多Pixmaps是否会出现任何问题和延迟?
方法2: 另一种方式是创建具有技能形状的多边形,并将它们用于碰撞检测。
a)如何为每个单独的技能定义多边形形状(有超过150个)?在浏览了一段时间后,我找到了这个有用的工具:http://www.aurelienribon.com/blog/projects/physics-body-editor/ 它允许手动创建多边形形状,然后将它们保存为可被libGDX应用程序读取的JSON文件。现在遇到了困难:
  • 物理体编辑器与Box2d相连(我没有使用)。为了一个微小的碰撞检测,我要么得添加整个Box2d物理引擎(我根本不需要),要么得编写一个自定义的BodyEditorLoader,这将是一项困难、复杂且耗时的任务。
  • 同一技能精灵的某些图像形状存在很大差异(例如第二个技能精灵示例)。当使用BodyEditor工具时,我不仅需要定义每个技能的形状,还需要定义每个技能的多个图像(最多12个)的形状。这将非常耗时,并且在实现这些数十个多边形形状时会变得非常混乱。

b) 如果有任何平滑的方式可以自动生成图像的多边形,那就可以解决问题。我只需将每个精灵部分连接到生成的多边形上,然后以此检查碰撞。然而,存在一些问题:

  • 有没有一种流畅的工具可以从图像中生成多边形形状(并且不需要太多时间)?
  • 我认为像这样的工具(如果存在)可能无法直接使用纹理。它可能需要像素图。但在多边形创建后,不需要保持像素图的加载。仍然是一个极其繁重的任务!

我的当前想法

我被困在这一点上,因为有几种可能的方法,但所有这些方法都有困难。在选择一条路线并继续编码之前,如果您能留下一些想法和知识,那将是非常好的。

libGDX 中可能包含有用的类和代码,可以在几秒钟内解决我的问题 - 因为我真的很新于 libGDX,所以我对它还不了解很多。

目前,我认为我会选择第一种方法:使用像素检测。这样,我在先前的 Android 游戏中实现了精确的碰撞检测。

你怎么看?

问候 Felix


你找到一个好的解决方案了吗?我也遇到了同样的问题。我不知道如何根据物理编辑器工具的坐标创建多边形,并在没有box2d的情况下检查碰撞。 - trinity420
3个回答

2
我个人认为像素对像素的碰撞会影响性能,并且在某些情况下我仍然会感到受骗 - (我被斧头的把手打中了?
如果是我,我会为每个技能添加一个“Hitbox”。 StreetFighter是一款使用这种技术的流行游戏。(新版本是3D的,但命中框碰撞仍然是2D的) 命中框可以随着动画逐帧更改。
在此期间,您可以在谷歌上搜索“Streetfighter hitbox”以查看示例图像。
对于您的斧头,可以沿着一端或两端的边缘定义一个矩形命中框,甚至可以覆盖整个金属斧头。
这使得它相当简单,而不必混乱于精确的多边形,但也不像每个像素都有自己的命中框那样过度消耗性能。

像素检测是一个繁重的任务,我已经进行了限制:虽然技能使用像素碰撞,但所有玩家和角色都被检测为一组矩形。一旦一个矩形和一个技能发生碰撞,就会检查技能受影响的像素是否透明。加快处理速度:只需检查每个第二或第四个像素即可。我并不想贬低你的想法,毕竟它已被证明在流行游戏中非常有效,但我认为将其实现到我的游戏中需要太多的努力:>100个技能精灵,每个精灵有~12张图片,将会产生大量的命中框+不够精确。 - Logende
@Logende,花时间在游戏中能够获得最高可玩性的部分上是非常重要的,我完全理解。优化应该是未来需要考虑的事情,如果你想扩展支持更多玩家和技能同时进行的游戏,可以通过改变碰撞方式来实现。 - DoubleDouble
(或者其他可以代替这个处理时间的东西)——更好的人工智能,随着你的操作在后台设置更多环境,同时在屏幕上显示更多内容等。 - DoubleDouble

1
我不知道这对你们是否还有意义,但我编写了一个小型Python脚本,返回图像边缘中点的像素位置。脚本可以改进,但对于现在来说已经足够了...
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))

如果有更新,您可以在这里找到它们:导出位置


看起来很有趣! :) 如果我理解正确的话,您使用脚本预先计算边缘,将它们存储到文本文件中,并在libGDX项目中使用该文本文件。把你的脚本添加到我的库文件中。我可能会尝试使用它,但首先我需要确保一切都能正常工作:游戏有100多种独特的技能,每种技能都有不同的动画。实现您的方法将是相当大的努力。 - Logende
@Logende,你说得对,我的脚本确实做了你说的事情...但请记住,它会导出图像中的所有点。在我的测试中,这意味着有太多的点要进行计算。更好的方法是获取脚本上面导出的一些点。 如果您有更多的精灵,我的建议是改进上述脚本以导出更少的点并读取更多的图像...这可能是游戏开发人员的新工具,我是说,box2d有一些类似的工具,但您必须手动完成(如果我没有弄错的话)! - rdenadai

1

我曾使用过你提到的那个身体编辑器,它有生成多边形和/或圆形的功能。我还用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


谢谢你的帮助!我仍然会尝试在没有Box2d的情况下解决问题,但是如果我被迫使用Box2d和Body Editor,我会使用你的提示 :) - Logende
身体编辑器使用Farseer来实现多边形的程序化生成。如果您能从这个链接中获取任何信息,它看起来很有趣。https://code.google.com/p/box2d-editor/source/browse/editor/src/aurelienribon/bodyeditor/maths/trace/TextureConverter.java - Johnathon Havens
你最终在这种情况下做了什么?你使用了Farseer代码吗? - Johnathon Havens
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Logende

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接