You are on page 1of 5

9/4/2020 One-way walls - Box2D tutorials - iforce2d

iforce2d <# insert awesome header graphics #>

Crushing immovable objects since 2011

Box2D tutorials | Cocos2d-x resources | Box2D JSON loader | R.U.B.E Box2D editor | R.U.B.E free | My apps | Blog ! | 日本語はこちら | Box2D中文教程

News Mar 20: R.U.B.E v1.7 released. Instanciable objects!


Feb 21: Added RUBE sample loader for Cocos2d-X v3.4.
Feb 17: Funny Fists released in iOS App store and Google Play store. (YouTube video)
2014
May 25: R.U.B.E v1.6 released. Samplers!
May 12: "On-the-fly" resource updating for Cocos2d-x
Apr 22: New blog post: Inverted pendulum control
Apr 7: On-screen logging class for Cocos2d-x
Mar 22: Downhill Supreme 2 released for iOS and Android
Jan 2: YouTube video: Making soft-body wheels in RUBE
2013
Oct 6: Check out Supplyfront RTS, my 7dRTS entry continued.
Sep 26: R.U.B.E v1.5 released. Customizable item labels, snap-to-grid, export filtering, full-text help search.
Sep 18: Added RUBE sample loader for Cocos2d-X.
More...

Box2D tutorial topics


Box2D C++ tutorials - One-way walls
Last edited: July 14 2013
Introduction
Setting up (Linux)
Setting up (Windows)
Halt! You should have a good understanding of
Setting up (Mac OSX) the basic tutorials before venturing further.
Testbed structure
Making a test
Bodies
Fixtures
One-way walls and platforms
World settings Many platform games allow the player to pass through a platform as they jump upwards, and then have it magically become
Forces and impulses solid so they can stand on it from above. Uh... I don't need to explain this do I? Let's just get started :)
Custom gravity
Moving at constant speed In Box2D all (non-sensor) fixtures are solid no matter which direction they are collided from, so we'll need to figure out a way
to alter this behavior. Since we are experts at working with contacts, we know that it's pretty easy to cancel the normal
Rotating to a given angle
collision response between two fixtures with b2Contact::SetEnabled(false). The question is when to do this. This is still a
Jumping subject of discussion among Box2D users and there is no standard way to tackle the question, given that it can depend on
Using debug draw many other factors in the game.
Drawing your own objects
User data There are a few ways you could decide when a contact should be disabled, and they all focus on determining which side of the
wall/platform the player came from:
Anatomy of a collision
Collision callbacks use the velocity of the player body in PreSolve
Collision filtering look at the contact normal in PreSolve
check the player velocity in BeginContact
Sensors
Ray casting The typical complications that arise with the first two of these are caused by not having quite enough information to work
World querying with. For example let's say you get a call to PreSolve so you know that the player and a platform are overlapping. You check
Removing bodies safely the velocity of the player body and find that it is moving down - if he is stepping on it from above the platform should be
solid... but he might also have jumped up from below and not quite made it far enough so is now falling back down again, in
The 'can I jump' question
which case the platform should not be solid.
Ghost vertices
Joints - overview This can be solved by adding more checks, for example using the location of the player body relative to the platform to see if
Joints - revolute he is close enough to the top to be allowed up onto the platform. But therein lies a lot of manual work to cater for many
Joints - prismatic different cases, and the method using the contact normal in PreSolve has similar issues, and the problem of the player
suddenly popping up above the platform when the normal changes.
FAQ / gotchas
The BeginContact method requires a little extra setup but it handles the generic case better and is more efficient since it does
Tutorial source code nothing in PreSolve. The basic idea is to look at the velocity of the contact points when the player first collides with the
platform in BeginContact and decide whether or not to enable the collision response, and this decision will remain in effect
Advanced topics until an EndContact occurs.

Conveyor belts The catch is, we'll need to make a little tweak to Box2D's source code to help this work. It's just a small change
Projected trajectory though, nothing to worry about.
Sticky projectiles
Hovercar suspension
Top-down car physics
Change to Box2D code
One-way walls Since we will be using BeginContact event which only occurs one time per collision, we can only use SetEnabled once to alter
Buoyancy the behavior of the contact. The problem is the contact will revert to being enabled again after each step. We could make a
Explosions note of which contacts we have disabled and then check the list of them every time in PreSolve, but that is kinda inefficient
Physics-driven particles and more work than I can be bothered with today. Or any day actually :)

Other topics So we'll just quietly sneak into b2Contact.cpp and comment out the line at the beginning of the Update function which re-
enables the contact. After you're done it should look like this:
Setting up (iPhone)
1 // Re-enable this contact.
2 //m_flags |= e_enabledFlag;

https://www.iforce2d.net/b2dtut/one-way-walls 1/5
9/4/2020 One-way walls - Box2D tutorials - iforce2d
That's it!
Note: Obviously you might not want to do this if you are relying on the default behavior for some reason...

Handling the simplest case

First let's take a look at how we could handle the simplest case, where the platform is static and level, and the player can
move up through it but not downwards. For the rest of this topic we'll reuse the platform shape, and we'll signify the 'back'
(the side from which it can be passed through ) with the small pointed corner, so the flat side should be solid. For the rest of
this topic I will be calling the flat side the 'front' or the 'face' of the platform, and when I say 'into the platform' I mean
approaching from the front side.

Here is some basic setup - keep a reference to the fixture and body in a class variable so we can use them later:

1 //setup platform shape for reuse


2 b2PolygonShape polygonShape;
3 b2Vec2 verts[5];
4 verts[0].Set( 0, -0.75);
5 verts[1].Set( 2.5, -0.5 );
6 verts[2].Set( 2.5, 0.5 );
7 verts[3].Set(-2.5, 0.5 );
8 verts[4].Set(-2.5, -0.5);
9 polygonShape.Set( verts, 5 );
10
11 //kinematic platform
12 {
13 b2BodyDef bodyDef;
14 bodyDef.type = b2_kinematicBody;
15 bodyDef.position.Set(0,10);
16 m_platformBody = m_world->CreateBody(&bodyDef);
17 m_platformFixture = m_platformBody->CreateFixture( &polygonShape, 0 );
18 }
19
20 //dynamic box
21 {
22 b2BodyDef bodyDef;
23 bodyDef.type = b2_dynamicBody;
24 bodyDef.position.Set(0,15);
25 b2Body* body = m_world->CreateBody(&bodyDef);
26
27 polygonShape.SetAsBox( 0.5, 0.5 );
28 b2FixtureDef fixtureDef;
29 fixtureDef.shape = &polygonShape;
30 m_world->CreateBody( &bodyDef )->CreateFixture( &polygonShape, 1 );
31 }

All we need to do in BeginContact is check whether each contact point is moving up or down. If either of them is moving
down, the platform should be solid. Just as a reminder that there could be two contact points, and also to help explain why
we need to check both of them, consider this case where the box is rotating as it hits the platform:

Obviously in this exaggerated screenshot the left corner is not touching the platform so there would be no contact point
there, but imagine that this angle is much smaller and both corners of the box are still touching the platform whilst the box is
rotating - rare, but it can happen.

Here is how a BeginContact could decide whether an incoming contact to the platform should be disabled.

1 void BeginContact(b2Contact* contact)


2 {
3 b2Fixture* fixtureA = contact->GetFixtureA();
4 b2Fixture* fixtureB = contact->GetFixtureB();
5
6 //check if one of the fixtures is the platform
7 b2Fixture* platformFixture = NULL;
8 b2Fixture* otherFixture = NULL;

https://www.iforce2d.net/b2dtut/one-way-walls 2/5
9/4/2020 One-way walls - Box2D tutorials - iforce2d
9 if ( fixtureA == m_platformFixture ) {
10 platformFixture = fixtureA;
11 otherFixture = fixtureB;
12 }
13 else if ( fixtureB == m_platformFixture ) {
14 platformFixture = fixtureB;
15 otherFixture = fixtureA;
16 }
17
18 if ( !platformFixture )
19 return;
20
21 b2Body* platformBody = platformFixture->GetBody();
22 b2Body* otherBody = otherFixture->GetBody();
23
24 int numPoints = contact->GetManifold()->pointCount;
25 b2WorldManifold worldManifold;
26 contact->GetWorldManifold( &worldManifold );
27
28 //check if contact points are moving downward
29 for (int i = 0; i < numPoints; i++) {
30 b2Vec2 pointVel =
31 otherBody->GetLinearVelocityFromWorldPoint( worldManifold.points[i] );
32 if ( pointVel.y < 0 )
33 return;//point is moving down, leave contact solid and exit
34 }
35
36 //no points are moving downward, contact should not be solid
37 contact->SetEnabled(false);
38 }
39
40 void EndContact(b2Contact* contact)
41 {
42 //reset the default state of the contact in case it comes back for more
43 contact->SetEnabled(true);
44 }

As you can see, most of this code is taken up with the usual chores of finding out which fixture is which etc. The two main
points to note are: we use GetLinearVelocityFromWorldPoint instead of the velocity of the body itself, so that we can check
the direction of movement of the relevant parts that collided with the platform. This is necessary if you allow your player body
to rotate.

The other important thing is to return the contact to the default (enabled) state when an EndContact occurs. This is
necessary because contacts exists as long as the AABBs of two fixtures continue to overlap, even if the fixtures themselves do
not overlap. If the player jumps just high enough to clear the top of the platform, but not high enough for the AABBs to
separate, the contact will remain disabled and he will fall back down again.

Handling the general case

By 'general case', I mean situations where the player and platform bodies can both be rotated or moving around. This is a
little tricker but still not too hard. We can't just check the y-component of the velocity of the contact point directly, we need
to convert that velocity into a relative velocity from the point of view of the platform, and then check it. Since the platform
itself can be moving or rotating, this means we'll also need to take into account the velocity of the contact point in the
platform.

We only need to change the last part of BeginContact. Here goes...

1 //check if contact points are moving into platform


2 for (int i = 0; i < numPoints; i++) {
3 b2Vec2 pointVelPlatform =
4 platformBody->GetLinearVelocityFromWorldPoint( worldManifold.points[i] );
5 b2Vec2 pointVelOther =
6 otherBody->GetLinearVelocityFromWorldPoint( worldManifold.points[i] );
7 b2Vec2 relativeVel = platformBody->GetLocalVector( pointVelOther - pointVelPlatform );
8 if ( relativeVel.y < 0 )
9 return;//point is moving into platform, leave contact solid and exit
10 }
11
12 //no points are moving into platform, contact should not be solid
13 contact->SetEnabled(false);

Close but no cigar

Unfortunately when I tried this in a more game-like scenario, there was one rather annoying little issue that showed up when
the other body approaches the platform at a very shallow angle. Consider how we are handling things so far, by checking the
vertical component of the approach velocity:

https://www.iforce2d.net/b2dtut/one-way-walls 3/5
9/4/2020 One-way walls - Box2D tutorials - iforce2d

At the red line there is a point where we abruptly switch between saying the platform is solid or not. For the most part this is
fine, the problem comes about when we have a body approaching from a very shallow angle, almost right along the red line,
and it is moving upwards but we want the platform to be solid. But when would we ever want that?

Actually, for a platform game using tiles we want that almost ALL the time. Even if the player body is moving along flat
ground, it is moving up and down a tiny bit as the collision response works to keep it on top of the ground fixture. Here is an
exaggerated image of this situation:

Suppose that the player body was moving upwards just a tiiiny bit at it hits the next tile. Our check in BeginContact would
disable that contact and the player would fall through the ground. So unfortunately, simply checking the approach velocity is
not quite enough - we'll need to use some other information as well.

For the other information, I decided to use the location of the contact point to decide whether the player should be allowed to
stand on the platform when approaching from a very shallow angle like this. The relevant part of BeginContact now looks like
this:

1 //check if contact points are moving into platform


2 for (int i = 0; i < numPoints; i++) {
3
4 b2Vec2 pointVelPlatform =
5 platformBody->GetLinearVelocityFromWorldPoint( worldManifold.points[i] );
6 b2Vec2 pointVelOther =
7 otherBody->GetLinearVelocityFromWorldPoint( worldManifold.points[i] );
8 b2Vec2 relativeVel = platformBody->GetLocalVector( pointVelOther - pointVelPlatform );
9
10 if ( relativeVel.y < -1 ) //if moving down faster than 1 m/s, handle as before
11 return;//point is moving into platform, leave contact solid and exit
12 else if ( relativeVel.y < 1 ) { //if moving slower than 1 m/s
13 //borderline case, moving only slightly out of platform
14 b2Vec2 relativePoint = platformBody->GetLocalPoint( worldManifold.points[i] );
15 float platformFaceY = 0.5f;//front of platform, from fixture definition :(
16 if ( relativePoint.y > platformFaceY - 0.05 )
17 return;//contact point is less than 5cm inside front face of platfrom
18 }
19 else
20 ;//moving up faster than 1 m/s
21 }
22
23 //no points are moving into platform, contact should not be solid
24 contact->SetEnabled(false);

Here we do the extra check when the approach velocity is less than 1 m/s in or out of the platform, and allow the platform to
stay solid if the contact point is within 5cm of the top of the platform... and that is the annoying part. Until now we could
treat all of our one-sided fixtures in the same way, but for this extra check we now need to know the 'height' of the front face
of the platform. Sure, we know this because we set the fixtures up ourselves, but it's extra work to have this info required in
the contact listener.

https://www.iforce2d.net/b2dtut/one-way-walls 4/5
9/4/2020 One-way walls - Box2D tutorials - iforce2d

Improvements

This method takes the rather simplistic assumption that the 'front' face of the platform/wall always has the normal (0,1) in
the local coordinates of the body, and our check results in approximately a 180 degree range around the front face in which
the platform/wall will be solid. If you wanted to restrict or extend the range in which the wall is solid, you could use a method
like that in the conveyor belts topic to check if the contact was inside the range you are interested in.

As I mentioned earlier, there does not seem to be any cut and dried solution for handling one-sided walls in Box2D. I think
this one does ok, but it's not as free from tweak-requiring as I had hoped. Check out the downloads below for a scene
involving a bunch of things commonly encountered in platform games. You may find the occasional glitch here and there :)

Source code

Here is the source code for those who would like to try it out for themselves. This is a 'test' for the testbed, based on Box2D
v2.3.0.

Testbed test (simple scene): iforce2d_OneWayWalls.h


Testbed test (platforms scene): iforce2d_OneWayWalls_demo.h

Linux binary
Windows binary

YouTube video

Not seeing the any comments below? Try changing the 'https' in the page URL to just 'http'
Click here: http://www.iforce2d.net/b2dtut/one-way-walls

Want to promote your Box2D game here for free?


Contact gmail 'iforce2d'
If you find this site useful please consider supporting it :)

https://www.iforce2d.net/b2dtut/one-way-walls 5/5

You might also like