Top-down conveyor belt with Box2D

I'm trying to code a conveyor belt for a project made with LibGDX and Box2D and I would like to make it like this one from Bomberman Land Touch.

As you can see, the entities standing on it should be able to go in the opposite direction moving slower, the ones not moving should just be transported towards the direction of the belt, and the ones going along its direction should go faster.

For a single direction belt I first detect when the center of an entity's body is inside the block, and then with body.applyLinearImpulse(direction) I move the body. This is working fine because it's applying a lower impulse than the one that the player uses to move and by checking the center of the entity instead of relying on the collide event, when the body is between two belts, it doesn't receive two impulses. This is the outcome:

I see two problems with that: the obvious one is that since it's checking the center of the body, as soon as it's in a belt with a different direction, it instantly changes direction without going to the center of the block. The other one is that while going backwards in a turn, it does a little "jump" due to the previous impulse.

I also tried to replicate the Bomberman mechanism by diving the corners into two fixtures but the result is quite the same (except a bug where the player doesn't move, probably because the center doesn't touch the next half of the block):

How can I mitigate these two problems or how can I rethink the whole structure/mechanism of the block?

Edit: I tried to implement the first part of @Theraot answer with this code:

if (active) {
for (Entity e : entities) {
// Check if the origin of the entity is inside the block
if (getBody().getFixtureList().get(0).testPoint(e.getOrigin())) {
// Take the x of the entity center if the inputDirection is horizontal, y otherwise
float entityAxis = inputDirection.y == 0 ? e.getOrigin().x : e.getOrigin().y;
// Same for the conveyor block
float blockAxis = inputDirection.y == 0 ? getOrigin().x : getOrigin().y;
Vector2 impulse = null;
// If they are really close
if (Math.abs(blockAxis - entityAxis) < 0.1f) {
impulse = outputDirection;
}
else {
impulse = inputDirection;
}
// Apply the impulse
e.getBody().applyLinearImpulse(impulse, e.getBody().getWorldCenter(), true);
}
}
}


and this is the result.

My general suggestion for your whole game mechanic is that you do not want to use the box2d physic forces to implement this. The collision detection might be useful, but low friction objects and impulses is not the right approach to simulate the conveyor belts in my opinion. The physics simulation does not match the reality nicely. But, here are my suggestions, and how they might help with each of your problems.

Make your avatar collision detectors for the conveyor belts much smaller

• Your collision detectors are too big. You actually want the fixture that you use to represent the centre of your character avatars to be much smaller, a small dot. I would also make the shape round.
• Then, relative to your tiles, the changes of direction will look close to being in the centre of your tiles.
• You might be trying to overload the fixture that you use for explosion collision detection? Use collision filters to put your conveyors and your player conveyor collision dot in a different group to your avatar bomb hit box and bomb explosions.

Don't use impulses or box2d forces to to move your characters

• Forces, impulses and friction are all great in box2d. So good for simulating free physics environments. But your game is not actually like that. It is quite constrained. Only x,y axis movement, from controller or conveyor. And movement only happens on what you want to be a grid of lines. There is not free movement to wobble around inside a tile, or there should not be.
• Rather use the collision events to register that one or many conveyor belts are acting on your avatar (You can have multiple during a transition from one to the other). Begin collision to register and deregister on end collision.
• Treat controller/player input in a similar way. Right arrow down register +1 for x-axis.
• During a step in your game loop, do your rudimentary physics. Look at forces wanting to be caused by any controller input or conveyor belts that are touching each avatar, do some math and move your character.

Transitions from one conveyor to another should be handled deterministically

• Probably a few options here, but in your game loop you need choose which conveyor to listen to, and this is where we can use precise points.
• Pick a point that is maybe just inside the edge of the end of each conveyor belt, but still in its centre axis.
• Select the belt end that your avatar is closest to.

I think you might still experience some drift if you go around a loop of belts for long enough, but you can make any movement also make an interpolated correction to be on the closest valid x or y coordinate depending on if you are moving vertically or horizontally.

They are not composite of two triangular belts.

I will be taking as example the bottom right corner in your loop. However, to keep this general, I need a bit of nomenclature...

Let us say that the belt corner has an input side and an output side. These are always on different axis of each other. So, each belt corner has an input axis and an output axis.

In the example, the input side is the top and the output side is the left. Then the input axis is vertical and the output axis is horizontal. Makes sense?

Now, what you will do:

• check if the object is on top of it, if it isn't do nothing.
• check if the object position on the input axis is before the center of the belt corner, if it is move the object along the input axis...
• if it isn't, move the object along the output axis.

I would use an special tile... However, since we are checking along the input axis, I suppose you could creat this from two belts dividing the tile. In the example, they would be one rectangular half tile on the top and one on the bottom. However, you would have to fix the visuals. Also, fix the problem of the center being between the tiles (here the output tile should have preference).

This should move the object down to the vertical position of the center to corner and then to the new direction. However, if the object did enter from below or from the left, it won't move down. And if it did enter from the right, it may or may not move down depending on the vertical position of the object.

I have not implemented this over a physics engine, although I do not expect it to be any harder.

After having a look at your code and the result you got...

1. To reiterate. Input and output, side and axis, are of the corner tile. They define its orientation, and are independent of the object that is being carried by it. For example, the bottom right corner of your loop has vertical (from the top) input and horizontal (to the left) output.
2. You do not check if the coordinate of the object and the belt are close (they are, you checked the object is on the belt), check if the entity is on the first part of the tile.

Ok, I understand that is confusing, however, with your code I can make an approximation of how to do it in your tool set...

Since you are using the direction for the impulse, I am assuming that you are representing the directions like this:

INPUT

Top    (going down)  = {x: 0, y: 1}
Bottom (going up)    = {x: 0, y:-1}
Left   (going right) = {x: 1, y: 0}
Right  (going left)  = {x:-1, y: 0}

OUTPUT

Top    (going up)    = {x: 0, y:-1}
Bottom (going down)  = {x: 0, y: 1}
Left   (going left)  = {x:-1, y: 0}
Right  (going right) = {x: 1, y: 0}


I am also assuming that the origin is the center.

Then:

bool horizontal = inputDirection.y == 0;
float blockAxis = 0;
float side = 0;
if (horizontal)
{
entityAxis = e.getOrigin().x;
side = inputDirection.x;
}
else
{
entityAxis = e.getOrigin().y;
side = inputDirection.y;
}

// ...

for (Entity e : entities) {
if (getBody().getFixtureList().get(0).testPoint(e.getOrigin())) {
float entityAxis = horizontal ? getOrigin().x : getOrigin().y;
// If the entity is on the input side
// (same sign check)
if (((blockAxis - entityAxis) * side) > 0) {
impulse = inputDirection;
}
else {
impulse = outputDirection;
}
// Apply the impulse
e.getBody().applyLinearImpulse(impulse, e.getBody().getWorldCenter(), true);
}
}


Notes:

1. Given the graphics, perhaps you want to use an invisible entity for the collision box of the avatar, placed around its feet, and use it for all checks. Then place the visible entity that holds the srpites of the avatar relative to it.
2. You might be interested in a way to get only nearby entities. In particular if you will be working with large maps or large quantities of entities.
• Thanks for the answer and sorry for the late reply. I edited the post and followed the first part of your suggestion because I didn't fully understand the second one: can you show me a picture of the "special tile"? And as you probably can see, I didn't understand how to handle the backwards walking, sorry. – F. Rossi Aug 19 '18 at 10:13
• @F.Rossi Expanded answer. Let me know if it give you trouble. – Theraot Aug 19 '18 at 11:02