📜 ⬆️ ⬇️

Box2d and libgdx

Good afternoon.

Not so long ago I began to study the work of Libgdx and found that there are not so many articles on this framework in Russian. Of course, I met articles in Russian, but they were only translations of offs. manuals. There were truth and articles where the developers tried to talk about their creations. But in one article, not to embrace everything, as a result, in such articles it was all ponamesh.

I set out to gradually write articles about various aspects / parts of LibGDX so that in the end, anyone could make a more or less workable version of their own toy. In this article I would like to tell you about Box2D, which is present in LibGDX.
image

Box2D is a real-time physics engine and is designed to work with two-dimensional physical objects. If you are developing a game with a side view / platformer, then this is the perfect solution.
All required classes are in the com.badlogic.gdx.physics.box2d package.
')
I took the architecture as the basis, which I described in one of my articles. The MVC pattern is used.

Gamescreen


GameScreen implements Screen, InputProcessor , which allows it to handle all user actions: clicks, etc.

It creates the necessary classes: MyWorld, WorldController, WorldRenderer.
The article, which took as a basis instead of MyWorld, uses World. I had to change it in order not to conflict with the class of the same name from the Box2D package.

@Override public void show() { //  ,        ,      MyWorld.CAMERA_WIDTH = MyWorld.CAMERA_HEIGHT* Gdx.graphics.getWidth()/Gdx.graphics.getHeight(); world = new MyWorld(); renderer = new WorldRenderer(world, MyWorld.CAMERA_WIDTH, MyWorld.CAMERA_HEIGHT,true); controller = new WorldController(world); Gdx.input.setInputProcessor(this); //     } 


Naturally, it is necessary to override all the processing methods: touchDown, touchUp and others.

As an example, redefine touchDown, touchUp so that our character can move left, right and jump.

 private void ChangeNavigation(int x, int y){ controller.resetWay(); //   ,  ,     () if(height-y > world.getPlayer().getPosition().y * renderer.ppuY) controller.upPressed(); //   ,  ,     if ( x< world.getPlayer().getPosition().x * renderer.ppuX) controller.leftPressed(); //   ,  ,     if (x> (world.getPlayer().getPosition().x )* renderer.ppuX) controller.rightPressed(); } @Override public boolean touchDown(int x, int y, int pointer, int button) { if (!Gdx.app.getType().equals(ApplicationType.Android)) return false; ChangeNavigation(x,y); return true; } @Override public boolean touchUp(int x, int y, int pointer, int button) { if (!Gdx.app.getType().equals(ApplicationType.Android)) return false; controller.resetWay(); return true; } @Override public boolean touchDragged(int x, int y, int pointer) { ChangeNavigation(x,y); return false; } 


In GameScreen , the user actions are processed and the controller is signaled. The key method here is render() , which fires after a certain amount of time and processes the logic and draws objects.

 @Override public void render(float delta) { Gdx.gl.glClearColor(0, 0, 0, 232F/255); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); controller.update(delta); renderer.render(delta); } 


Myworld



MyWorld will be just a container of objects. It will also contain a link to a copy of the World class from the package. It is necessary to define objects.

 World world; // Player player; //  Array<MovingPlatform> platforms = new Array<MovingPlatform>(); // public MovingPlatform groundedPlatform = null; 


Well, create objects:

 public MyWorld(){ width = 30; height = 8; //   ,    () world = new World(new Vector2(0, -20), true); createWorld(); } private void createWorld(){ BodyDef def = new BodyDef(); def.type = BodyType.DynamicBody; Body boxP = world.createBody(def); player = new Player(boxP); player.getBody().setTransform(3.0f, 4.0f, 0); player.getBody().setFixedRotation(true); Body box = createBox(BodyType.StaticBody, 1, 1, 0); for(int i = 0; i < 10; i++) { box = createBox(BodyType.DynamicBody, (float)Math.random(), (float)Math.random(), 13); box.setTransform((float)Math.random() * 10f - (float)Math.random() * 10f, (float)Math.random() * 10 + 6, 0); } platforms.add(new MovingPlatform(world, 3F, 3, 1,0.25F, 2, 0, 2)); for(int i=1;i<width; ++i){ Body boxGround = createBox(BodyType.StaticBody, 1F, 1F, 0); boxGround.setTransform(i,0,0); } } private Body createBox(BodyType type, float width, float height, float density) { BodyDef def = new BodyDef(); def.type = type; Body box = world.createBody(def); PolygonShape poly = new PolygonShape(); poly.setAsBox(width, height); box.createFixture(poly, density); poly.dispose(); return box; } 


BodyDef is a body definition that contains all data about a solid body. You can reuse this definition for various objects.

BodyType - body type. It can be one of three possible:
static - zero mass, zero speed, can only be moved by software;
kinematic - zero mass, non-zero speed, can be shifted;
dynamic - positive mass, non-zero speed, can be shifted.

static should be used for objects that do not need to move. As an example: the floor walls.

Body , in fact, the body itself.
Body objects can be moved, rotated using the setTransform() method.

Player


Player class is a character:

 public class Player { final static float MAX_VELOCITY = 3f; public final static float SPEED = 5f; public final static float SIZE = 0.8f; public Fixture playerPhysicsFixture; public Fixture playerSensorFixture; Body box; public Player(Body b){ box = b; PolygonShape poly = new PolygonShape(); poly.setAsBox(0.4f, 0.4f); playerPhysicsFixture = box.createFixture(poly, 3); poly.dispose(); CircleShape circle = new CircleShape(); circle.setRadius(0.41f); circle.setPosition(new Vector2(0, 0f)); playerSensorFixture = box.createFixture(circle, 1); setFriction(10F); circle.dispose(); box.setBullet(true); } public Body getBody(){ return box; } //    public void setFriction(float f){ playerSensorFixture.setFriction(f); playerPhysicsFixture.setFriction(f); } public Vector2 getPosition(){ return box.getPosition(); } public Vector2 getVelocity() { return velocity; } Vector2 velocity = new Vector2(); public void update(float delta) { Vector2 vel = box.getLinearVelocity(); velocity.y = vel.y; box.setLinearVelocity(velocity); if(isJump) {box.applyLinearImpulse(0, 8, box.getPosition().x, box.getPosition().y); isJump = false;} } boolean isJump = false; public void jump(){ isJump = true; } public void resetVelocity(){ getVelocity().x =0; getVelocity().y =0; } } 


In this example, I showed that in Box2D you can create objects from kinematic pairs. In this case, the player consists of a glued rectangle and a circle.

To move objects in principle, you can in different ways, until the explicit change of position. But in Box2D there are two special methods for this:
applyLinearImpulse() - set the momentum to the object. As in the real world, if you push an object, then according to the impulse, the body will move until the friction suppresses the impulse.
setLinearVelocity() - set the speed of movement. Unlike the impulse, the speed will be constant until you change it.

I personally use setLinearVelocity() to move along the X axis. While you keep your finger on the wheelbarrow, the character will move, when you release, the character will stop. If you use applyLinearImpulse() to move horizontally, the character will still move some time after you remove your finger from the screen.
In principle, you can sometimes applyLinearImpulse() for left / right movement. As an example, if the character moves on a slippery surface (ice, as an example).

box.setBullet(true); indicates that the body should be treated like a bullet when it detects collisions.

Movingplatform


MovingPlatform is a moving platform.

 public class MovingPlatform { Body platform; Vector2 pos = new Vector2(); Vector2 dir = new Vector2(); Vector2 vel = new Vector2(); float maxDist = 0; float width; //Vector2 l = new Vector2(); public MovingPlatform(World world, float x, float y, float width, float height, float dx, float dy, float maxDist) { platform = createBox(world, BodyType.KinematicBody, width, height, 1); this.width = width; pos.x = x; pos.y = y; dir.x = dx; dir.y = dy; vel.x = dx; vel.y = dy; this.maxDist = maxDist; platform.setTransform(pos, 0); platform.getFixtureList().get(0).setUserData("p"); platform.setUserData(this); } public void resume(){ dir.x= vel.x; dir.y = vel.y; } public void pause(){ vel.x = dir.x; vel.y = dir.y; dir.x = 0; dir.y = 0; platform.setLinearVelocity(dir); } public void update(float deltaTime) { if(dir.x < 0 && platform .getPosition().x < pos.x-maxDist) { platform .getPosition().x =pos.x; dir.mul(-1); } if(dir.x > 0 && platform .getPosition().x> pos.x+maxDist) { platform .getPosition().x =pos.x+maxDist; dir.mul(-1); } platform.setLinearVelocity(dir); } private Body createBox(World world,BodyType type, float width, float height, float density) { BodyDef def = new BodyDef(); def.type = type; Body box = world.createBody(def); PolygonShape poly = new PolygonShape(); poly.setAsBox(width/2, height/2); Fixture f = box.createFixture(poly, density); poly.dispose(); return box; } } 

The platform will move a certain distance from its original position.

Worldcontroller


In WorldController , the logic is updated in response to user actions in the update() method.

 boolean grounded ; public void update(float delta) { Array<MovingPlatform> platforms = world.getPlatforms(); //   for(int i = 0; i < platforms.size; i++) { MovingPlatform platform = platforms.get(i); platform.update(Math.max(1/60.0f, delta)); } grounded = isPlayerGrounded(Gdx.graphics.getDeltaTime()); //  processInput(); //   world.getPlayer().update(delta); } 


To process user clicks and specify a new direction of movement is necessary.
  //            private void processInput() { Player player = world.getPlayer(); //    if (keys.get(Keys.LEFT)) player.getVelocity().x =- Player.SPEED; //    if (keys.get(Keys.RIGHT)) player.getVelocity().x = Player.SPEED; //   if (keys.get(Keys.UP)) //    if(grounded) // player.jump(); //    if(!grounded) //  world.getPlayer().setFriction(0f); //   else{ if(keys.get(Keys.RIGHT) || keys.get(Keys.LEFT)) world.getPlayer().setFriction(0.2f); else world.getPlayer().setFriction(100f); } } 


It remains only to write a method to check whether the character is on the ground. To do this, you must obtain a list of all the contacts of objects. Among them, choose character contacts. If among the contacts there is an object that is below the character, therefore it is on a hard surface.
  private boolean isPlayerGrounded(float deltaTime) { world.groundedPlatform = null; List<Contact> contactList = world.getWorld().getContactList(); for(int i = 0; i < contactList.size(); i++) { Contact contact = contactList.get(i); if(contact.isTouching() && (contact.getFixtureA() == world.getPlayer().playerSensorFixture || contact.getFixtureB() == world.getPlayer().playerSensorFixture)) { Vector2 pos = world.getPlayer().getPosition(); WorldManifold manifold = contact.getWorldManifold(); boolean below = true; for(int j = 0; j < manifold.getNumberOfContactPoints(); j++) { below &= (manifold.getPoints()[j].y < pos.y - 0.4f); } if(below) { if(contact.getFixtureA().getUserData() != null && contact.getFixtureA().getUserData().equals("p")) { world.groundedPlatform = (MovingPlatform)contact.getFixtureA().getBody().getUserData(); } if(contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("p")) { world.groundedPlatform = (MovingPlatform)contact.getFixtureB().getBody().getUserData(); } return true; } return false; } } return false; } 


Depending on where the character is, the friction force changes. With zero friction, the character will not move with the platform, but will remain in place, as if to slide.


It is logical to assume that it is necessary to expose more friction. I watched foreign manuals ... This method does not always work.

You can then go the other way: to change the friction force is not the character, and the contact. That is, in the isPlayerGrounded method, write something like:
 if (!keys.get(Keys.LEFT) && !keys.get(Keys.RIGHT)) contact.setFriction(200F); else contact.setFriction(0F); 

Then, in case the character just stands, he will move along with the platform due to a lot of friction. If the character needs to move, then reduce friction.



Actually everything. Our character can move left and right, as well as jump. It can also move randomly generated blocks.

It is necessary now only to render objects.

World renderer


In the WorldRenderer class, WorldRenderer are WorldRenderer .
 public class WorldRenderer { Box2DDebugRenderer renderer; public static float CAMERA_WIDTH = 10f; public static float CAMERA_HEIGHT = 15f; public float ppuX; public float ppuY; MyWorld world; public OrthographicCamera cam; public WorldRenderer(MyWorld world, float w, float h, boolean debug) { renderer = new Box2DDebugRenderer(); this.world = world; CAMERA_WIDTH = w; CAMERA_HEIGHT = h; ppuX = (float)Gdx.graphics.getWidth() / CAMERA_WIDTH; ppuY = (float)Gdx.graphics.getHeight() / CAMERA_HEIGHT; this.cam = new OrthographicCamera(CAMERA_WIDTH, CAMERA_HEIGHT); SetCamera(CAMERA_WIDTH / 2f, CAMERA_HEIGHT / 2f); } public void SetCamera(float x, float y){ this.cam.position.set(x, y,0); this.cam.update(); } public void dispose(){ world.dispose(); } public void render(float delta) { renderer.render(world.getWorld(), cam.combined); world.getWorld().step(delta, 4, 4); } } 


The rendering itself in the render() method takes place. This example is used to draw a Box2DDebugRenderer in debug mode to display the borders of objects. In the final version of your application in the render() method, you need to display any sprites for all objects.

world.getWorld().step(delta, 4, 4); indicates the frequency with which to update physics. In most cases, I think, there is no need to update physics more often than drawing takes place.
PS I just started to study Box2D myself, so I will be glad to any tips and criticism.

Source: https://habr.com/ru/post/161977/


All Articles