You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断LibGDX Box2D中ChainShape的碰撞侧(Pong游戏场景)

问题描述

我正在学习LibGDX,用Box2D处理碰撞与移动,开发一款简单的Pong游戏。需求是:球碰到左右墙时(对方得分),销毁球的刚体并在屏幕中心生成新球;碰到上下墙则不执行此操作。

目前我用ChainShape创建了闭合的游戏边框刚体,也实现了ContactListener处理碰撞,但不知道如何仅在碰到左右墙时触发目标逻辑。我想过把左右墙单独用EdgeShape分离,但找不到实现方法,Contact、Fixture和ChainShape里没有相关方法,网上也没查到有效信息。

当前代码如下:

ContactListener代码

public final class CollisionListener implements ContactListener {
  @Override
  public void beginContact(Contact contact) {
    var fixtureA = contact.getFixtureA();
    var fixtureB = contact.getFixtureB();
    
    if (fixtureA.getUserData() != "border" && fixtureB.getUserData() != "border") {
      return;
    }

    //TODO: Only trigger for left and right walls

    var object = fixtureA.getUserData() == "border" ? fixtureB : fixtureA;
    Entity entity = (Entity)object.getUserData();

    // Logic for creating new body
  }
}

边框创建代码

private void createBorders() {
  var bodyDef = new BodyDef();
  bodyDef.type = BodyType.StaticBody;
  bodyDef.position.set(0, 0);

  var body = world.createBody(bodyDef);

  var chain = new ChainShape();
  chain.createLoop(
    new Vector2[] {
      new Vector2(Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MAX_Y),
      new Vector2(Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MIN_Y),
      new Vector2(Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MIN_Y),
      new Vector2(Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MAX_Y)
    }
  );

  body.createFixture(chain, 0f);
  body.setUserData("border");
  chain.dispose();
}

解决方案

核心思路是将上下墙与左右墙拆分为独立的Fixture,给每个Fixture设置不同的UserData标记,这样就能在碰撞检测时精准区分碰撞的是哪部分墙。

步骤1:拆分边框为四个独立EdgeShape

放弃用ChainShape做整体闭合边框,分别创建上下左右四个EdgeShape,每个对应一个Fixture,并给每个Fixture设置明确的UserData(比如left-wall、right-wall、top-wall、bottom-wall)。

修改后的createBorders代码:

private void createBorders() {
    BodyDef bodyDef = new BodyDef();
    bodyDef.type = BodyType.StaticBody;
    bodyDef.position.set(0, 0);
    Body body = world.createBody(bodyDef);

    // 右墙:x=VIEWPORT_MAX_X,从顶部到底部
    EdgeShape rightWall = new EdgeShape();
    rightWall.set(Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MAX_Y, Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MIN_Y);
    FixtureDef rightFixtureDef = new FixtureDef();
    rightFixtureDef.shape = rightWall;
    rightFixtureDef.density = 0f;
    Fixture rightFixture = body.createFixture(rightFixtureDef);
    rightFixture.setUserData("right-wall");
    rightWall.dispose();

    // 左墙:x=VIEWPORT_MIN_X,从顶部到底部
    EdgeShape leftWall = new EdgeShape();
    leftWall.set(Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MAX_Y, Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MIN_Y);
    FixtureDef leftFixtureDef = new FixtureDef();
    leftFixtureDef.shape = leftWall;
    leftFixtureDef.density = 0f;
    Fixture leftFixture = body.createFixture(leftFixtureDef);
    leftFixture.setUserData("left-wall");
    leftWall.dispose();

    // 上墙:y=VIEWPORT_MAX_Y,从左到右
    EdgeShape topWall = new EdgeShape();
    topWall.set(Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MAX_Y, Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MAX_Y);
    FixtureDef topFixtureDef = new FixtureDef();
    topFixtureDef.shape = topWall;
    topFixtureDef.density = 0f;
    Fixture topFixture = body.createFixture(topFixtureDef);
    topFixture.setUserData("top-wall");
    topWall.dispose();

    // 下墙:y=VIEWPORT_MIN_Y,从左到右
    EdgeShape bottomWall = new EdgeShape();
    bottomWall.set(Constants.VIEWPORT_MIN_X, Constants.VIEWPORT_MIN_Y, Constants.VIEWPORT_MAX_X, Constants.VIEWPORT_MIN_Y);
    FixtureDef bottomFixtureDef = new FixtureDef();
    bottomFixtureDef.shape = bottomWall;
    bottomFixtureDef.density = 0f;
    Fixture bottomFixture = body.createFixture(bottomFixtureDef);
    bottomFixture.setUserData("bottom-wall");
    bottomWall.dispose();
}

步骤2:修改ContactListener区分左右墙

在碰撞回调中,判断碰撞的Fixture是否为left-wall或right-wall,仅当符合条件时才执行销毁旧球、生成新球的逻辑。

修改后的CollisionListener代码:

public final class CollisionListener implements ContactListener {
    @Override
    public void beginContact(Contact contact) {
        Fixture fixtureA = contact.getFixtureA();
        Fixture fixtureB = contact.getFixtureB();

        // 区分墙和球的Fixture
        Fixture wallFixture = null;
        Fixture ballFixture = null;
        if ("left-wall".equals(fixtureA.getUserData()) || "right-wall".equals(fixtureA.getUserData())) {
            wallFixture = fixtureA;
            ballFixture = fixtureB;
        } else if ("left-wall".equals(fixtureB.getUserData()) || "right-wall".equals(fixtureB.getUserData())) {
            wallFixture = fixtureB;
            ballFixture = fixtureA;
        }

        // 非左右墙碰撞则直接返回
        if (wallFixture == null) {
            return;
        }

        // 执行销毁旧球、生成新球逻辑
        Entity ballEntity = (Entity) ballFixture.getUserData();
        world.destroyBody(ballEntity.getBody());
        createBallAtCenter(); // 替换为你自己的新球生成方法
    }

    // 示例:屏幕中心生成新球的方法
    private void createBallAtCenter() {
        // 此处编写你的新球创建逻辑
    }
}

额外说明

  • 不使用ChainShape的原因:ChainShape的Fixture是一个整体,无法区分碰撞发生在哪一段边上,拆分独立EdgeShape是最直接的解决方案。
  • 替代方案:也可以用碰撞过滤(Filter),给左右墙设置不同的CategoryBits,再在碰撞时判断,但用UserData标记更直观,适合小型项目。

内容的提问来源于stack exchange,提问作者Copha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:43:18