title: Physics and gravity in Andengine/Box2D platformer
body: <p>I'm putting together a 2D platformers using Andengine with the Box2D extension using the PhysicsJumpExample (<a href="https://code.google.com/p/andengineexamples/source/browse/src/org/anddev/andengine/examples/PhysicsJumpExample.java" rel="nofollow">https://code.google.com/p/andengineexamples/source/browse/src/org/anddev/andengine/examples/PhysicsJumpExample.java</a>) as a base. I'm also using the TMX extension for level loading.</p>
  
  <p>My level, player controls etc seem to be working OK but I'm really struggling with the Box2D physics in terms of gravity. The AnimatedSprite being controlled is being acted upon by gravity but the descent is sooo slow when using "Earth" gravity (~-9.8). If I increase this gravity then the descent is quicker but then the Sprite bounces up and down on the solid "floor" and sideways movement is greatly reduced (due to friction I would assume).</p>
  
  <p>Finally, for some reason, every example I've looked at has used a positive value for gravity when instantiating the PhysicsWorld but I've had to reverse it in order to have gravity act "as normal".</p>
  
  <p>Obviously I'm doing something very wrong and would greatly appreciate some pointers in getting this sorted out.</p>
  
  <p>Code for the 2 classes in question below.</p>
  
  <pre><code>public class GameActivity extends SimpleBaseGameActivity {
  
  private final static String TAG = "GameActivity";
  
  private final int CAMERA_WIDTH = 800;
  private final int CAMERA_HEIGHT = 480;
  
  private HUD _hud;
  private Text _score;
  private Text _debug;
  private PhysicsWorld mPhysicsWorld;
  private PhysicsHandler _playerPhysics;
  private TMXTiledMap _map;
  private PlayerSprite _player;
  private BitmapTextureAtlas mOnScreenControlTexture;
  private TextureRegion mOnScreenControlBaseTextureRegion;
  private TextureRegion mOnScreenControlKnobTextureRegion;
  private Scene mScene;
  private Font mFont;
  
  private ITexture mPlayerTexture;
  private TiledTextureRegion mPlayerTextureRegion;
  
  @Override
  public EngineOptions onCreateEngineOptions() {
      final BoundCamera camera = new BoundCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
      EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE_FIXED, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), camera);
      engineOptions.getAudioOptions().setNeedsMusic(true).setNeedsSound(true);
      engineOptions.setWakeLockOptions(WakeLockOptions.SCREEN_ON);
      return engineOptions;
  }
  
  @Override
  public void onCreateResources() throws IOException {
  }
  
  @Override
  public Scene onCreateScene() {
      this.mEngine.registerUpdateHandler(new FPSLogger());
      this.mPhysicsWorld = new PhysicsWorld(new Vector2(0, 0 - (SensorManager.GRAVITY_EARTH)), false);
      this.mScene = new Scene();
      this.mScene.getBackground().setColor(Color.BLACK);
      try {
          createFont();
          createWorld();
          createPlayer();
          createControllers();
          createHUD();
      } catch (Exception e) {
          Log.e(TAG, e.getMessage());
      }
      this.mScene.registerUpdateHandler(this.mPhysicsWorld);
      return mScene;
  }
  
  private void createFont() {
      FontFactory.setAssetBasePath("media/fonts/");
      final ITexture mainFontTexture = new BitmapTextureAtlas(getTextureManager(), 256, 256, TextureOptions.NEAREST);
      mFont = FontFactory.createStrokeFromAsset(getFontManager(), mainFontTexture, getAssets(), "game.otf", 40f, true, android.graphics.Color.WHITE, 2f,
          android.graphics.Color.BLACK);
      mFont.load();
  }
  
  private void createHUD() {
      Log.d(TAG, "createHud");
      _hud = new HUD();
      _score = new Text(mEngine.getCamera().getCameraSceneWidth() - 80, mEngine.getCamera().getCameraSceneHeight() - 20, mFont, "0123456789", new TextOptions(
          HorizontalAlign.RIGHT), getVertexBufferObjectManager());
      _score.setText("0000");
      _hud.attachChild(_score);
      _debug = new Text(150, mEngine.getCamera().getCameraSceneHeight() - 20, mFont, "abcdefghijklmnopqrstuvwxyz0123456789.,()[] ", new TextOptions(HorizontalAlign.LEFT),
          getVertexBufferObjectManager());
      _debug.setText("");
      _hud.attachChild(_debug);
      mEngine.getCamera().setHUD(_hud);
  }
  
  private void createPlayer() throws Exception {
      Log.d(TAG, "createPlayer");
      this.mPlayerTexture = new AssetBitmapTexture(getTextureManager(), getAssets(), "media/sprites/p1.png");
      this.mPlayerTextureRegion = TextureRegionFactory.extractTiledFromTexture(this.mPlayerTexture, 5, 2);
      this.mPlayerTexture.load();
      _player = new PlayerSprite(50, 320, this.mPlayerTextureRegion, mEngine.getCamera(), this.mPhysicsWorld, getVertexBufferObjectManager());
      _player.setUserData(_player.getBody());
      _playerPhysics = new PhysicsHandler(_player);
      this.mPhysicsWorld.setContactListener(new ContactListener() {
  
          @Override
          public void preSolve(Contact contact, Manifold oldManifold) {
              // TODO Auto-generated method stub
  
          }
  
          @Override
          public void postSolve(Contact contact, ContactImpulse impulse) {
              // TODO Auto-generated method stub
  
          }
  
          @Override
          public void endContact(Contact contact) {
              // TODO Auto-generated method stub
  
          }
  
          @Override
          public void beginContact(Contact contact) {
              if (contact == null || contact.getFixtureA() == null || contact.getFixtureB() == null)
                  return;
              if (contact.getFixtureA().getBody().getUserData() == null || contact.getFixtureB().getBody().getUserData() == null)
                  return;
              if (contact.getFixtureA().getBody().getUserData().toString().equals("player") || contact.getFixtureB().getBody().getUserData().toString().equals("player")) {
                  _player.setJumping(false);
              }
          }
      });
      _player.registerUpdateHandler(_playerPhysics);
      this.mScene.attachChild(_player);
  }
  
  private void createControllers() {
      Log.d(TAG, "createControllers");
      BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("media/");
      mOnScreenControlTexture = new BitmapTextureAtlas(getTextureManager(), 256, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
      mOnScreenControlBaseTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mOnScreenControlTexture, this, "images/controller.png", 0, 0);
      mOnScreenControlKnobTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(mOnScreenControlTexture, this, "images/controller_top.png", 128, 0);
      getTextureManager().loadTexture(mOnScreenControlTexture);
      final float x1 = mOnScreenControlBaseTextureRegion.getWidth();
      final float y1 = (this.mEngine.getCamera().getCameraSceneHeight() - mOnScreenControlBaseTextureRegion.getHeight());
      final DigitalOnScreenControl velocityOnScreenControl = new DigitalOnScreenControl(x1, y1, this.mEngine.getCamera(), this.mOnScreenControlBaseTextureRegion,
          this.mOnScreenControlKnobTextureRegion, 0.1f, true, getVertexBufferObjectManager(), new IOnScreenControlListener() {
  
              @Override
              public void onControlChange(BaseOnScreenControl pBaseOnScreenControl, float pValueX, float pValueY) {
                  float x = 0, y = 0;
                  if (pValueX == -1 &amp;&amp; pValueY == 0) {
                      if (_player.getDirection() != PlayerDirection.LEFT) {
                          _player.setRotation(-1);
                          _player.animate(75);
                      }
                      x = pValueX * 8;
                      y = pValueY * 8;
                      _player.setDirection(PlayerDirection.LEFT);
                  } else if (pValueX == -1 &amp;&amp; pValueY == 1) {
                      if (!_player.isJumping()) {
                          if (_player.getDirection() != PlayerDirection.UP_LEFT) {
                              _player.animate(25);
                          }
                          x = SensorManager.GRAVITY_EARTH * -10;
                          y = SensorManager.GRAVITY_EARTH * -10;
                          _player.setDirection(PlayerDirection.UP_LEFT);
                      }
                  } else if (pValueX == 0 &amp;&amp; pValueY == 1) {
                      if (!_player.isJumping()) {
                          if (_player.getDirection() != PlayerDirection.UP) {
                              _player.animate(75);
                          }
                          y = SensorManager.GRAVITY_EARTH * 2;
                          _player.setDirection(PlayerDirection.UP);
                      }
                  } else if (pValueX == 1 &amp;&amp; pValueY == 1) {
                      if (!_player.isJumping()) {
                          if (_player.getDirection() != PlayerDirection.UP_RIGHT) {
                              _player.animate(25);
                          }
                          x = SensorManager.GRAVITY_EARTH * 10;
                          y = SensorManager.GRAVITY_EARTH * -10;
                          _player.setDirection(PlayerDirection.UP_RIGHT);
                      }
                  } else if (pValueX == 1 &amp;&amp; pValueY == 0) {
                      if (_player.getDirection() != PlayerDirection.RIGHT) {
                          _player.animate(75);
                      }
                      x = pValueX * 8;
                      y = pValueY * 8;
                      _player.setDirection(PlayerDirection.RIGHT);
                  } else if (pValueX == 1 &amp;&amp; pValueY == -1) {
                      _player.setDirection(PlayerDirection.DOWN_RIGHT);
                  } else if (pValueX == 0 &amp;&amp; pValueY == 1) {
                      _player.setDirection(PlayerDirection.DOWN);
                  } else if (pValueX == -1 &amp;&amp; pValueY == -1) {
                      _player.setDirection(PlayerDirection.DOWN_LEFT);
                  } else {
                      _player.setDirection(PlayerDirection.NONE);
                      if (!_player.isJumping()) {
                          _player.stopAnimation(0);
                      }
                  }
                  final Vector2 velocity = Vector2Pool.obtain(x, y);
                  _player.getBody().setLinearVelocity(velocity);
                  Vector2Pool.recycle(velocity);
                  _debug.setText(_player.getDirection().toString() + ": " + (pValueX * 60) + "," + (pValueY * 60));
              }
          });
      velocityOnScreenControl.getControlBase().setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
      velocityOnScreenControl.getControlBase().setAlpha(0.5f);
  
      this.mScene.setChildScene(velocityOnScreenControl);
  }
  
  private void createWorld() {
      Log.d(TAG, "createWorld");
      try {
          final TMXLoader tmxLoader = new TMXLoader(getAssets(), getTextureManager(), TextureOptions.NEAREST, getVertexBufferObjectManager());
          _map = tmxLoader.loadFromAsset("media/levels/1/1.tmx");
          final TMXLayer tmxLayer = _map.getTMXLayers().get(0);
          tmxLayer.detachSelf();
          this.mScene.attachChild(tmxLayer);
          ((BoundCamera) this.mEngine.getCamera()).setBounds(0, 0, tmxLayer.getHeight(), tmxLayer.getWidth());
          ((BoundCamera) this.mEngine.getCamera()).setBoundsEnabled(true);
  
          float height = tmxLayer.getHeight();
          float width = tmxLayer.getWidth();
  
          final IShape bottom = new Rectangle(0, height - 2, width, 2, getVertexBufferObjectManager());
          bottom.setVisible(false);
          final IShape top = new Rectangle(0, 0, width, 2, getVertexBufferObjectManager());
          top.setVisible(false);
          final IShape left = new Rectangle(0, 0, 2, height, getVertexBufferObjectManager());
          left.setVisible(false);
          final IShape right = new Rectangle(width - 2, 0, 2, height, getVertexBufferObjectManager());
          right.setVisible(false);
  
          final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 1f);
          PhysicsFactory.createBoxBody(this.mPhysicsWorld, bottom, BodyType.StaticBody, wallFixtureDef);
          PhysicsFactory.createBoxBody(this.mPhysicsWorld, top, BodyType.StaticBody, wallFixtureDef);
          PhysicsFactory.createBoxBody(this.mPhysicsWorld, left, BodyType.StaticBody, wallFixtureDef);
          PhysicsFactory.createBoxBody(this.mPhysicsWorld, right, BodyType.StaticBody, wallFixtureDef);
  
          this.mScene.attachChild(bottom);
          this.mScene.attachChild(top);
          this.mScene.attachChild(left);
          this.mScene.attachChild(right);
  
          float yOffset = _map.getTMXLayers().get(0).getHeight();
          for (final TMXObjectGroup group : _map.getTMXObjectGroups()) {
              if (group.getTMXObjectGroupProperties().containsTMXProperty("solid", "true")) {
                  for (final TMXObject object : group.getTMXObjects()) {
                      final Rectangle rect = new Rectangle(object.getX() + object.getWidth() / 2, yOffset - object.getY() - object.getHeight() / 2, object.getWidth(),
                          object.getHeight(), getVertexBufferObjectManager());
                      final FixtureDef boxFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 1f);
                      Body b = PhysicsFactory.createBoxBody(this.mPhysicsWorld, rect, BodyType.StaticBody, boxFixtureDef);
                      b.setUserData("solid");
                      rect.setVisible(false);
                      rect.setZIndex(0);
                      this.mScene.attachChild(rect);
                  }
              }
          }
      } catch (final TMXLoadException e) {
          Log.e(TAG, e.getMessage());
      }
  }
  
  @Override
  public void onDestroyResources() throws IOException {
      Log.d(TAG, "onDestroyResources: Cleanup scenes/resources");
      super.onDestroyResources();
  }
  
  @Override
  protected void onDestroy() {
      super.onDestroy();
      System.exit(0);
  }
  </code></pre>
  
  <p>}</p>
  
  <pre><code>public class PlayerSprite extends AnimatedSprite {
  
  public enum PlayerDirection {
      NONE, LEFT, UP_LEFT, UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT
  }
  
  private Body _body;
  private PlayerDirection _direction = PlayerDirection.NONE;
  private boolean _jumping = false;
  
  public PlayerSprite(float pX, float pY, ITiledTextureRegion region, Camera camera, PhysicsWorld physics, VertexBufferObjectManager vbo) {
      super(pX, pY, region, vbo);
      createPhysics(camera, physics);
      camera.setChaseEntity(this);
  }
  
  public Body getBody() {
      return _body;
  }
  
  public PlayerDirection getDirection() {
      return _direction;
  }
  
  public void setDirection(PlayerDirection direction) {
      _direction = direction;
  }
  
  public boolean isJumping() {
      return _jumping;
  }
  
  public void setJumping(boolean jumping) {
      _jumping = jumping;
  }
  
  private void createPhysics(final Camera camera, PhysicsWorld physics) {
      _body = PhysicsFactory.createBoxBody(physics, this, BodyType.DynamicBody, PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f));
      _body.setUserData("player");
      physics.registerPhysicsConnector(new PhysicsConnector(this, _body, true, true));
  }
  </code></pre>
  
  <p>}</p>