Using Box2d in Libgdx Game Part III

Hi,

In this post we would make a box2d object manager to handle creation/deletion of objects.

First we would make some changes in the BaseBoxObject class made in the last post to add option of deleting an object from the world and also an abstract function to draw the texture which would have to be implemented in the child classes.

BaseBoxObject:

public abstract class BaseBoxObject{
        ...........
       protected boolean isActive;
       public abstract void Draw(SpriteBatch sp);

       public void Destroy(World world){
                if(isActive){
                         world.destroyBody(body);
                         isActive=false;
                }
       } 
       public void ChangeBodyIndex(int newIndex){
            userData.Set(newIndex,userData.GetCollisionGroup());
            body.setUserData(userData);
       } 
}

BoxObjectManager:

public class BoxObjectManager{
      ArrayList<BaseBoxObject> objects;
      ArrayList<BaseBoxObject> deleteList;
      World world;
      public BoxObjectManager(Vector2 gravity){ 
           world=new World(gravity,true);
          objects=new ArrayList<BaseBoxObject>();
          deleteList=new ArrayList<BaseBoxObject>();
      }

      public BaseBoxObject AddObject(Vector2 pos,int objType,int collisionGroup){
           BaseBoxObject temp;
           //CREATE different objects depending on the type considering we have to child classes 
           // Object1 and Object2 inheriting the base class BaseBoxObject
           swtich (objType){
                case OBJECT1: temp=new Object1(world,pos,objects.size(),collisionGroup);
                              break;
                case OBJECT2: temp=new Object2(world,pos,objects.size(),collisionGroup);
                              break;

           }
           objects.add(temp);
           return temp;
      } 

      public void Update(float dt){
           for(BaseBoxObject del:deleteList){
                   int index=del.GetBoxId();
                   BaseBoxObject replace=objects.get(objects.size()-1);
                   replace.setBodyIndex(index);  //Update the index to the object to be removed
                   objects.set(index,replace);
                   objects.remove(objects.size()-1);
           }
           for(BaseBoxObject obj:objects){
                   obj.Update(dt);
           }
           deleteList.clear();
      }
      public void Draw(SpriteBatch sp){
           for(BaseBoxObject obj:objects){
                   obj.Draw(sp);
           }
      }
      public void AddForDeletion(BaseBoxObject obj){
             deleteList.add(obj);
      } 
}

In update we first go through the objects in the delete list and take the last object from the main objects arraylist and set it to the delete objects position. We also change their ids  (replace.setBodyIndex(index)) so we don’t have issues while fetching the objects from the array. If the objects are stored in a sorted manner (maybe sorted by position), then every time an object is removed you would have to update the objects after that index i.e. increment their indices by one and move them all up by one in the array.

So in main game class we would instantiate a BoxObjectManager object and use it add to add objects to the world and delete them. Below is an example.

public class MainGame(){
          ......
          BoxObjectManager boxObjectManager;
          void Init(){
                 boxObjectManager=new BoxObjectManager(gravity);
                  ......
           }
           void Render(SpriteBatch sp){
                    boxObjectManager.Draw(sp);
           }
           void Update(float dt){
                    boxObjectManager.Update(dt);
             }
           void AddObject(Vector2 pos,int type,int collisionGroup){
                   Object1 obj=(Object1)boxObjectManager.GetNewObject(pos,type,collisionGroup);
                   //Set Object properties ..
                   obj.SetTexture(....);
                   obj.SetSensor(...);
                    .....
            }
            void DeleteObject(BaseBoxObject obj){
                    boxObjectManager.AddForDeletion(obj);
            }
}

So this is a rough implementation of how we can create all box2d objects in one class and have unique references to them. This code can be improved a lot further and it is just a direction of how to handle the objects.

Sensor :

In box2d we can set a property called “sensor” to true which makes body transparent in the  sense that when a body collides with a sensor body it does not deflect or change direction i.e. no collision response is done though we receive the information that the body has collided. So they are really useful in implement collectibles like stars,healths,powerups as usually we don’t want the protagonist to deflect on colliding with them.

Deleting Object:

In balloon shooter we never really destroyed a box2d body during a game. We created pools of arrows as they were the only dynamicaly created bodies (others numbers were fixed during a level) so whenever a arrow collided, we just set a custom boolean flag “active” to false so while handling collisions we ignored that object. For balloons we just made them inactive so for a collision nothing was done with them. We don’t know how efficient that might be but we had very few bodies in the screen so we got away with it. And when a level was over/restarted we deleted all the bodies/fixtures in the world, cleared the arraylist and loaded them again.

For checking collisions we have to implement an interface called ContactListener which would handle collisions between different types of objects in the game and handle the responses.

We would implement ContactListener in BoxObjectManager as it contains references for all the objects so it would be easy to handle deletion.

There are 4 functions which have to be implemented when using ContactListener.

BeginContact:  This event is called when two fixtures begin to overlap or start to collide. Usually we would check here if the body is to be removed or not.

EndContact: This event is called when the overlap between the two fixtures ends. This can be called when we delete a body/fixture as the contact would stop existing for that body/fixture.

PreSolve:This is called after the collision is detected but before the response has been calculated. This is good if we don’t want the object to behave in normal way for e.g. maybe tunnel through a wall from one direction and not from another.

PostSolve: In this event we get the collision impulse results.  But mostly if we would like to change the collision response we would do it in PreSolve.

public class BoxObjectManager implements ContactListener{
............. 
      public BoxObjectManager(){
            .................
            //After initializing the world the conactlistener has to enabled for the world
            world.setContactListener(this); 
      }
      @Override
      public void beginContact(Contact contact) {
             // TODO Auto-generated method stub
            Fixture f1=contact.getFixtureA();
            Body b1=f1.getBody();
            Fixture f2=contact.getFixtureB();
            Body b2=f2.getBody();
            BoxUserData userData1 = (BoxUserData) b1.getUserData();
            BoxUserData userData2 = (BoxUserData) b2.getUserData();
            //Depending on type of body we can handle 
           if(userData1.GetCollisionGroup()==OBJECT1  && userData2.GetCollisionGroup()==OBJECT2)
                     HandleObject12Collision(userData1,userData2);
           if(userData1.GetCollisionGroup()==OBJECT2  && userData2.GetCollisionGroup()==OBJECT1)
                     HandleObject12Collision(userData2,userData1);
      }
      void HandleObject12Collision(BoxUserData userData1,BoxUserData userData2){
                   Object1 object1=(Object1)objects.get(userData1.GetBoxID());
                   Object2 object2=(Object2)objects.get(userData2.GetObjectID());
                   //Change health/color etc of object1/2
                  //Maybe then add object2 for deletion
                   AddForDeletion(object2)
       } 
      @Override
       public void endContact(Contact contact) {
             // TODO Auto-generated method stub
      }
      @Override
       public void preSolve(Contact contact, Manifold oldManifold) {
      }
      @Override
       public void postSolve(Contact contact, ContactImpulse impulse) {
       // TODO Auto-generated method stub
      }

}

In this way we can write specific responses for collisions between different types of classes. The BoxUserData class holds the variable collisionGroup/objectType value to tell which type of object it is. Then we get the base object from the list and recast it to that child class to change its properties. Then we can remove the body from the world or leave it as it is. This gives a really good control for handling collisions of different types of bodies.

BodyTypes:

Static: This type as the name suggests is a static body. It has a 0 mass and it cannot move. Usually ground/walls or stationary enemies can be of this type. Fixture on static bodies can only detect a collision with a fixture on dynamic body.

Kinematic: This is a really useful body type for games which include moving platforms. Kinematic body also has 0 mass but it can be given a velocity and it only detects collision with a fixture of a dynamic body.

Dynamic: This is used for all other types, most of the enemies, bullets etc are dynamic. While creating dynamic bodies its good to give a density to the body and then let box calculate its mass depending on its shape.

This is all for today. Thank you for the patience. Please leave any queries/suggestions/feedback in comments section. We look forward to hearing from you.

Tagged with: , , , ,
Posted in Tutorials
12 comments on “Using Box2d in Libgdx Game Part III
  1. JG says:

    hi thank you for speaking to me on the other article about the box 2d example code and I have gone away and I have managed to do the thing I talked about which was to click on the screen and change the direction of the object but I have now started to look at handling the collisions and I do not fully understand how to access each collision so that I can make something happen in the code when the two objects collide. Could you maybe show or explain(I know you have kind of done this in this article) how I would detect a collision using the collision listener. for example detecting the collision of the ball and ground in the example code and then making something happen when they do collide for example a counter that increases by one each time there is a collision

  2. Astalos says:

    Could anyone help me?
    I want to jump mu character in my game, but i have problem with it. When I call method applyForce() or applyImpulse() on body, the character suddenly change it’s position to the highest point of jump and slowly fall down. Why doesn’t it also slowly go up?

  3. Astalos says:

    I have resolved my problem, but I have one more. I wrote about it there:
    http://box2d.org/forum/viewtopic.php?f=3&t=8568&p=36476#p36476
    Does anyone know why it is?

  4. Niemand says:

    Hi,

    your posts are really helping me. Thanks alot!
    But unfortunately I have problems understanding the delete method. I am quite new to libGDX and game gevelopment and my mother tongue is not english, so please don’t make so complicated sentences :D .
    Could you explain each step in detail again? Why do you create a copy of the last object and just adjust the ID of this object? And why do you add this object (if I got the set method right) and remove it in the next line again?
    I hope you can understand my question and look forward to hearing from you!

    • Rahul Srivastava says:

      Hi,
      I am not sure which particular steps you are having doubts about so i will explain the deleting process.
      Suppose we have 20 items in the arrayList objects . We are storing in each object the position of the object in the array as index. We use this to fetch the object using objects.get(index). Now for example 5th object has to be deleted. If we do objects.remove(4) it would remove the object shift all the items by one.5 would become 4, 6 would become 5 etc.
      Now either we loop through the whole arraylist and update index position stored each object to point to the location in the arraylist. Or we take the last object and copy it at the 5th object location and then remove the last object. So in this way we dont have to loop through the full array list. When we copy a object to new location in the arraylist we update the position stored in it to point to the index of the arraylist.

      If you don’t have many objects then instead of removing an object from the main arraylist(arrayList objects in the post) just maintain a boolean to mark it as active/inactive. On inactive don’t show any texture for it and just remove it from box2d world body list by calling objects destroy function. This is what we did in our game balloon shooter. We did not many objects at a time in the game.

      Secondly we cannot delete box2d bodies inside contact listener override function like beginContact, endContact. Reason is that these functions can be called between update functions. So we store all the objects to be deleted in an array.
      On every update we loop through the objects to be deleted (objects in deleteList) and then delete it from box2d .

      Hope it helps. Thanks

      • Niemand says:

        Yes, this helped me a lot. Thank you!

        The problem was, that I didn’t see any delete method for the object, which has to be deleted. I didn’t know you can just remove them by overwriting them…

        I hope my question was not totally stupid, but as I said already said: I am a beginner and libGDX is my first real engine.

        Thanks.

  5. Where is definition “// Object1 and Object2 inheriting the base class BaseBoxObject”

    I don’t see.

    • Rahul Srivastava says:

      The Object1 and Object2 classes are not defined in the post. There are just shown as reference on how to use the derived classes in the ContactListener.

Try our games



Error: Twitter did not respond. Please wait a few minutes and refresh this page.