In Portal 2, there is a concept of an "excursion funnel", a beam of light that moves physical objects in a certain direction.

Here is a link to a demonstration.

I added a simple implementation that lets me float upward against gravity (I can't give you an example I've tried as I've lost it due to the undo buffer size being too small), however I've been having a struggle generalizing it to work with any angle.

My code so far:

// on funnel collision with body:

// on each update, for each body that is in collision:
    float s = sin(this->body->GetAngle()), c = cos(this->body->GetAngle());

    b2Vec2 force = {body->GetWorld()->GetGravity().x * -s * 2.f, body->GetWorld()->GetGravity().y * -c * 2.f};
    for(ServerEnt* e : collidingWith) {
        e->body->ApplyForceToCenter(e->body->GetMass() * force, true);

// on funnel end collision with body:

This almost works, except Portal 2 funnels don't let objects fall through it, they sort of push the objects toward the center of the nearest cross-section of the funnel, which is where my code fails, or rather doesn't do anything about.

  • 1
    \$\begingroup\$ How about just disabling gravity while the player is inside the funnel? \$\endgroup\$
    – Quentin
    Nov 7, 2018 at 10:46
  • \$\begingroup\$ As seen in the question I've already tried that with SetGravityScale. It stops the acceleration but not the velocity, the player just goes through the tunnel. \$\endgroup\$
    – mid
    Nov 7, 2018 at 14:54
  • 1
    \$\begingroup\$ If that's the case, can't you directly set the velocity of the body to zero or something suitably low on entry, before applying the new force? \$\endgroup\$
    – ZombieTfk
    Nov 7, 2018 at 16:39
  • \$\begingroup\$ That would look completely unnatural. \$\endgroup\$
    – mid
    Nov 7, 2018 at 16:57
  • \$\begingroup\$ Hmm, yes that's true. If you want to do it gradually, you could apply a a force which is a normal to the current direction of the gravity tunnel, with a magnitude proportional to the player's current velocity vector \$\endgroup\$
    – ZombieTfk
    Nov 7, 2018 at 18:14

1 Answer 1

  1. On collision with the field, set gravity to 0.
  2. While colliding with the field, multiply your velocity by a number between 0 and 1, like 0.999; This is called "damping", it simulates being inside a viscous fluid.
  3. While colliding with the field, apply a force along whatever vector you want to push the body in.This in combination with the damping should push the object along at a more or less constant velocity.
  4. If you want to get fancy, add a restoring force toward the center of the field that increases proportionally with the distance of the object to the center of the field. This will prevent the object from falling out of the field. This basically models a spring between the body and the center of the field.

As an example of (4), if your field is a circle, you can do it like this:

// pos is the position of the body. The field is a circle, but only
// its center matters. Gain is a multiplier on the force.
Vector2 restoringForce(Vector2 pos, Circle field, float gain) {
    // Vector pointing from the middle of the circle to the player.
    Vector2 diff = pos - field.center;
    // The restoring force points toward the center of the circle
    // and is proportional to the distance.
    return -gain * diff;

If your field is a line, you can do it like this:

// Note this is totally untested and some signs might need to change.
Vector2 restoringForce(Vector2 pos, Line field, float gain) {
    // Direction of the line segment.
    Vector2 fieldDirection = (field.end - field.start).normalized();
    // Lift the direction to 3D.
    Vector3 fieldDirection3D = new Vector3(fieldDirection.x, fieldDirection.y, fieldDirection.z);
    // Find a vector pointing perpendicular to the field vector.
    Vector3 normal = (new Vector3(0, 0, 1)).cross(fieldDirection3D);
    // Project the normal back to 2D.
    Vector2 normal2D = new Vector3(normal.x, normal.y);
    // Define a point on the line.
    Vector2 center = (field.end + field.start) * 0.5f;
    // Find the difference between the body position and the point
    // in question.
    Vector2 diff = pos - center;
    // This is the distance along the normal to the line.
    float dist = diff.dot(normal2D);
    // The restoring force points toward the line segment along the
    // normal, and is proportional to the distance to the center.
    return -gain * dist * normal;
  • \$\begingroup\$ Have to wait 11 more hours to give you your bounty, but it works! Have to adjust a couple values still but thanks! \$\endgroup\$
    – mid
    Nov 7, 2018 at 18:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .