Libgdx box2d ContactListener 非常有问题
Libgdx box2d ContactListener is extremely glitchy
我 运行 在使用 libgdx 开发我的小型 2D 横向卷轴平台游戏时遇到了这种情况。主要问题是 beginContact(Contact contact)
在明确需要时没有被调用。以下是一些简化的代码片段,可提供快速概览:
ContactListener
class 记录传感器与地面之间的每次接触:
public class WorldContactListener implements ContactListener
{
Player player;
public WorldContactListener(Player player, World world)
{
this.player = player;
world.setContactListener(this);
}
@Override
public void beginContact(Contact contact)
{
Object userDataA = contact.getFixtureA().getUserData();
Object userDataB = contact.getFixtureB().getUserData();
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "start");
}
}
@Override
public void endContact(Contact contact)
{
Object userDataA = contact.getFixtureA().getUserData();
Object userDataB = contact.getFixtureB().getUserData();
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "stop");
}
}
@Override
public void preSolve(Contact contact, Manifold oldManifold)
{
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse)
{
}
}
玩家创建:
//Main rectangle:
BodyDef bodyDef = new BodyDef();
bodyDef.position.set(spawnCoordinates);
bodyDef.type = BodyType.DynamicBody;
body = world.createBody(bodyDef);
FixtureDef fixtureDef = new FixtureDef();
PolygonShape shape = new PolygonShape();
shape.setAsBox(5 / Tomb.PPM, 14 / Tomb.PPM);
fixtureDef.shape = shape;
fixtureDef.friction = FRICTION; //1
fixtureDef.restitution = RESTITUTION; //0
body.createFixture(fixtureDef).setUserData(Tomb.UserData.PLAYER);
//Circle-shaped sensor:
fixtureDef = new FixtureDef();
CircleShape feet = new CircleShape();
feet.setPosition(new Vector2(0, -16 / Tomb.PPM));
feet.setRadius(10 / Tomb.PPM);
fixtureDef.shape = feet;
fixtureDef.isSensor = true;
body.createFixture(fixtureDef).setUserData(Tomb.UserData.PLAYER_FEET);
球员动作:
private void handleInput(float delta)
{
if(Gdx.input.isKeyJustPressed(Input.Keys.UP))
{
body.applyLinearImpulse(new Vector2(0, JUMP_POWER), body.getWorldCenter(), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.RIGHT) && body.getLinearVelocity().x <= MAX_HORIZONTAL_VELOCITY)
{
body.applyLinearImpulse(new Vector2(HORIZONTAL_SPEED, 0), body.getWorldCenter(), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.LEFT) && body.getLinearVelocity().x >= -MAX_HORIZONTAL_VELOCITY)
{
body.applyLinearImpulse(new Vector2(-HORIZONTAL_SPEED, 0), body.getWorldCenter(), true);
}
}
从 Tiled Map Editor 导入的 .tmx 地图的初始化。值得注意的是,整个地形是一个 PolygonMapObject
,因此在这种情况下,不需要 for
循环。
protected void defineGround(int layerIndex)
{
for(PolygonMapObject object : map.getLayers().get(layerIndex).getObjects().getByType(PolygonMapObject.class))
{
BodyDef bodyDef = new BodyDef();
ChainShape chainShape = new ChainShape();
FixtureDef fixtureDef = new FixtureDef();
Body body;
Polygon polygon = object.getPolygon();
bodyDef.type = BodyType.StaticBody;
bodyDef.position.set(polygon.getX() / Tomb.PPM, polygon.getY() / Tomb.PPM);
body = world.createBody(bodyDef);
float[] scaledVertices = new float[polygon.getVertices().length];
for(int i = 0; i < polygon.getVertices().length; i++)
{
scaledVertices[i] = polygon.getVertices()[i] / Tomb.PPM;
}
chainShape.createChain(scaledVertices);
fixtureDef.shape = chainShape;
body.createFixture(fixtureDef).setUserData(Tomb.UserData.GROUND);
}
}
最后,自我解释的图片,显示游戏屏幕本身和带有 WorldContactListener
日志的 eclipse 控制台:
尝试爬上斜坡(或任何非平坦表面)时会发生同样的事情:
我已经尝试了所有可能的传感器形状和尺寸变化,因此此行为不是由 CircleShape
的尺寸引起的。可能是什么情况?或者任何不涉及 ContactListener
?
的解决方法
问题是一次会发生多次碰撞。在你的最后一张图片上,你可以看到你的玩家和多边形之间仍然存在 (2-1 + 2-1) = 2 次碰撞。
这是我的示例解决方案:
使用函数 incCollision() 和 decCollision() 制作一些碰撞计数器 class,也许一些 bool isCollison() 会告诉您是否确实存在任何碰撞。
编辑您的 ContactListener class:
public void beginContact(Contact contact)
{
...
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "start");
counter.incCollision();
}
}
public void endContact(Contact contact)
{
...
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "stop");
counter.decCollision();
}
}
我 运行 在使用 libgdx 开发我的小型 2D 横向卷轴平台游戏时遇到了这种情况。主要问题是 beginContact(Contact contact)
在明确需要时没有被调用。以下是一些简化的代码片段,可提供快速概览:
ContactListener
class 记录传感器与地面之间的每次接触:
public class WorldContactListener implements ContactListener
{
Player player;
public WorldContactListener(Player player, World world)
{
this.player = player;
world.setContactListener(this);
}
@Override
public void beginContact(Contact contact)
{
Object userDataA = contact.getFixtureA().getUserData();
Object userDataB = contact.getFixtureB().getUserData();
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "start");
}
}
@Override
public void endContact(Contact contact)
{
Object userDataA = contact.getFixtureA().getUserData();
Object userDataB = contact.getFixtureB().getUserData();
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "stop");
}
}
@Override
public void preSolve(Contact contact, Manifold oldManifold)
{
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse)
{
}
}
玩家创建:
//Main rectangle:
BodyDef bodyDef = new BodyDef();
bodyDef.position.set(spawnCoordinates);
bodyDef.type = BodyType.DynamicBody;
body = world.createBody(bodyDef);
FixtureDef fixtureDef = new FixtureDef();
PolygonShape shape = new PolygonShape();
shape.setAsBox(5 / Tomb.PPM, 14 / Tomb.PPM);
fixtureDef.shape = shape;
fixtureDef.friction = FRICTION; //1
fixtureDef.restitution = RESTITUTION; //0
body.createFixture(fixtureDef).setUserData(Tomb.UserData.PLAYER);
//Circle-shaped sensor:
fixtureDef = new FixtureDef();
CircleShape feet = new CircleShape();
feet.setPosition(new Vector2(0, -16 / Tomb.PPM));
feet.setRadius(10 / Tomb.PPM);
fixtureDef.shape = feet;
fixtureDef.isSensor = true;
body.createFixture(fixtureDef).setUserData(Tomb.UserData.PLAYER_FEET);
球员动作:
private void handleInput(float delta)
{
if(Gdx.input.isKeyJustPressed(Input.Keys.UP))
{
body.applyLinearImpulse(new Vector2(0, JUMP_POWER), body.getWorldCenter(), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.RIGHT) && body.getLinearVelocity().x <= MAX_HORIZONTAL_VELOCITY)
{
body.applyLinearImpulse(new Vector2(HORIZONTAL_SPEED, 0), body.getWorldCenter(), true);
}
if(Gdx.input.isKeyPressed(Input.Keys.LEFT) && body.getLinearVelocity().x >= -MAX_HORIZONTAL_VELOCITY)
{
body.applyLinearImpulse(new Vector2(-HORIZONTAL_SPEED, 0), body.getWorldCenter(), true);
}
}
从 Tiled Map Editor 导入的 .tmx 地图的初始化。值得注意的是,整个地形是一个 PolygonMapObject
,因此在这种情况下,不需要 for
循环。
protected void defineGround(int layerIndex)
{
for(PolygonMapObject object : map.getLayers().get(layerIndex).getObjects().getByType(PolygonMapObject.class))
{
BodyDef bodyDef = new BodyDef();
ChainShape chainShape = new ChainShape();
FixtureDef fixtureDef = new FixtureDef();
Body body;
Polygon polygon = object.getPolygon();
bodyDef.type = BodyType.StaticBody;
bodyDef.position.set(polygon.getX() / Tomb.PPM, polygon.getY() / Tomb.PPM);
body = world.createBody(bodyDef);
float[] scaledVertices = new float[polygon.getVertices().length];
for(int i = 0; i < polygon.getVertices().length; i++)
{
scaledVertices[i] = polygon.getVertices()[i] / Tomb.PPM;
}
chainShape.createChain(scaledVertices);
fixtureDef.shape = chainShape;
body.createFixture(fixtureDef).setUserData(Tomb.UserData.GROUND);
}
}
最后,自我解释的图片,显示游戏屏幕本身和带有 WorldContactListener
日志的 eclipse 控制台:
尝试爬上斜坡(或任何非平坦表面)时会发生同样的事情:
我已经尝试了所有可能的传感器形状和尺寸变化,因此此行为不是由 CircleShape
的尺寸引起的。可能是什么情况?或者任何不涉及 ContactListener
?
问题是一次会发生多次碰撞。在你的最后一张图片上,你可以看到你的玩家和多边形之间仍然存在 (2-1 + 2-1) = 2 次碰撞。
这是我的示例解决方案: 使用函数 incCollision() 和 decCollision() 制作一些碰撞计数器 class,也许一些 bool isCollison() 会告诉您是否确实存在任何碰撞。
编辑您的 ContactListener class:
public void beginContact(Contact contact)
{
...
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "start");
counter.incCollision();
}
}
public void endContact(Contact contact)
{
...
if((userDataA == Tomb.UserData.GROUND || userDataB == Tomb.UserData.GROUND)
&& (userDataA == Tomb.UserData.PLAYER_FEET || userDataB == Tomb.UserData.PLAYER_FEET))
{
Gdx.app.log("collision", "stop");
counter.decCollision();
}
}