Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

21. November 2014

 

In the last video tutorial we used a graphic instead of text to create a Hello World.  This is because drawing text is actually a multi step process in LibGDX and not really appropriate for a first tutorial.  It is however perfect for a second tutorial, so here we are! ;)

 

In this video we explore the difference between TTF and Bitmap fonts, show how to run the Hiero font generation tool in both Eclipse and IntelliJ IDEA then create and save a bitmap font.  We then explore the code needed to show a bitmap font on screen, including how to measure the results, apply color and text justification.

 

The video is available in up to 1080P on YouTube by clicking here.

 

The source code:

Initial Example – Loading a font and drawing text:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class gdxtext extends ApplicationAdapter {
   SpriteBatch batch;
    BitmapFont font;

   @Override
   public void create () {
      batch = new SpriteBatch();
        font = new BitmapFont(Gdx.files.internal("Papy.fnt"));
   }

   @Override
   public void render () {
      Gdx.gl.glClearColor(0, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      batch.begin();

        //Example One -- Drawing Text
        font.draw(batch,"Hello World",Gdx.graphics.getWidth()/2,Gdx.graphics.getHeight()/2);

        batch.end();
   }
}

 

Example 2 – Measuring and centering text:

BitmapFont.TextBounds bounds = font.getBounds("Hello World");
font.draw(batch,"Hello World",
       Gdx.graphics.getWidth()/2 - bounds.width/2,
       Gdx.graphics.getHeight()/2 + bounds.height/2);

Example 3 – Multi-line Text

String debugString = "I took one, one cause you left me\n"
           + "Two, two for my family\n"
           + "Three, three for my heartache\n"
           + "Four, four for my headaches\n"
           + "Five, five for my sorrow\n";
   BitmapFont.TextBounds bounds = font.getMultiLineBounds(debugString);

 

Example 4 -- Center justified text in the colour purple

    font.setColor(Color.PURPLE);
    font.drawMultiLine(batch,
            debugString,
            0,
            Gdx.graphics.getHeight()/2 + bounds.height/2,
            Gdx.graphics.getWidth(),
            BitmapFont.HAlignment.CENTER
            );

Programming , , ,

18. November 2014

 

In this video tutorial, we create our first LibGDX project.  This tutorial covers that basic layout of a LibGDX application, adding new assets to a project and looks at how the initial project is composed and how multiple platforms are supported.  We then go through the initial code, then look in depth at coordinate systems, then move on to creating a sprite.  Finally we look at positioning, scaling and rotating a sprite with a SpriteBatch.

 

This tutorial assumes you already have your development environment setup and know how to run a LibGDX application in your chosen IDE.  If you do no, please watch these videos first.

 

The following is the video tutorial.  It was recorded in 1080p and the complete version can be viewed here.

 

 

The following is the final code generated:

 

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class HelloWorld extends ApplicationAdapter {
   SpriteBatch batch;
   Texture img;
   Sprite sprite;
   
   @Override
   public void create () {
      batch = new SpriteBatch();
      img = new Texture("HelloWorld.png");
      sprite = new Sprite(img);
      sprite.setPosition(
            Gdx.graphics.getWidth()/2 - sprite.getWidth()/2,
            Gdx.graphics.getHeight()/2 - sprite.getHeight()/2);
      sprite.setScale(1.0f,2.0f);
   }

   @Override
   public void render () {
      Gdx.gl.glClearColor(0, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      batch.begin();
      batch.draw(sprite, sprite.getX(),sprite.getY(),sprite.getWidth()/2,
            sprite.getHeight()/2,sprite.getWidth(),
            sprite.getHeight(),sprite.getScaleX(),
            sprite.getScaleY(),sprite.getRotation());
      batch.end();
   }
}

 

Here is the image that was used in this tutorial:

HelloWorld

Programming , , ,

12. November 2014

 

The title pretty much says it all.  The installation process to get a fully working Java, Android and GWT development environment up and running can be a bit tricky at times.  These three videos walk through the entire process, from downloading and installing a Java JDK, then the Android SDK and GWT then finally creating your first LibGDX project.  The second video then looks at working with IntelliJ IDEA with LIbGDX, the third video shows the same thing for Eclipse.  Each of these videos walks through loading a project, then running a Desktop, Android then HTML5 project.  Each of the videos is hosted on YouTube and is available in 1080p.  Click the link above the video below to open it directly in YouTube.

 

Part 1: Configuring a Java Development Environment for LibGDX and Android Development

 

 

Part 2: Using IntelliJ IDEA with LibGDX

 

 

Part 3: Using Eclipse with LibGDX

 

Programming , ,

25. September 2014

 

Remember in our earlier example where we used a ContactListener to check for a collision between two types of FixtureDef and how ungainly the check was?  Remember when I said there was a much better way of doing this?  Well, welcome the the better way, Filters.

 

Filters are actually rather simple, except they use bitmasking, a concept that might be somewhat new to you.  I actually went into some detail on the use of bitmasks in this tutorial if you need some details.  Basically its about using individual bits in a number as “on/off” flags.  Basically it allows you to track multiple values in a single variable.  In Box2D, there are a pair of bitmask, the categoryBits and maskBits.  Those names can be a bit confusing but don’t worry about that.

 

Basically you can think of categoryBits as saying “This is what I am” and maskBits as “This is what I collide with”.  Keep in mind, you can set multiple bits for either, so you can both be multiple things and collide with multiple things.  That may still sound somewhat confusing, so let’s look at an example.  This is a modification of the code from the previous tutorial.  In this case, we want our two sprites to NOT collide with each other, but to collide with the edge that represents the ground in the world.

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;

public class Physics4 extends ApplicationAdapter {
    SpriteBatch batch;
    Sprite sprite,sprite2;
    Texture img;
    World world;
    Body body,body2;
    Body bodyEdgeScreen;

    Matrix4 debugMatrix;
    OrthographicCamera camera;

    final float PIXELS_TO_METERS = 100f;


    final short PHYSICS_ENTITY = 0x1;    // 0001
    final short WORLD_ENTITY = 0x1 << 1; // 0010 or 0x2 in hex

    @Override
    public void create() {
        batch = new SpriteBatch();
        img = new Texture("badlogic.jpg");

        // Create two identical sprites slightly offset from each other vertically
        sprite = new Sprite(img);
        sprite.setPosition(-sprite.getWidth()/2,-sprite.getHeight()/2 +200);
        sprite2 = new Sprite(img);
        sprite2.setPosition(-sprite.getWidth()/2 + 20,-sprite.getHeight()/2 + 400);

        world = new World(new Vector2(0, -1f),true);

        // Sprite1's Physics body
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set((sprite.getX() + sprite.getWidth()/2) /
                        PIXELS_TO_METERS,
                (sprite.getY() + sprite.getHeight()/2) / PIXELS_TO_METERS);

        body = world.createBody(bodyDef);


        // Sprite2's physics body
        BodyDef bodyDef2 = new BodyDef();
        bodyDef2.type = BodyDef.BodyType.DynamicBody;
        bodyDef2.position.set((sprite2.getX() + sprite2.getWidth()/2) /
                        PIXELS_TO_METERS,
                (sprite2.getY() + sprite2.getHeight()/2) / PIXELS_TO_METERS);

        body2 = world.createBody(bodyDef2);

        // Both bodies have identical shape
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(sprite.getWidth()/2 / PIXELS_TO_METERS, sprite.getHeight()
                /2 / PIXELS_TO_METERS);

        // Sprite1
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = 0.1f;
        fixtureDef.restitution = 0.5f;
        fixtureDef.filter.categoryBits = PHYSICS_ENTITY;
        fixtureDef.filter.maskBits = WORLD_ENTITY;


        // Sprite2
        FixtureDef fixtureDef2 = new FixtureDef();
        fixtureDef2.shape = shape;
        fixtureDef2.density = 0.1f;
        fixtureDef2.restitution = 0.5f;
        fixtureDef2.filter.categoryBits = PHYSICS_ENTITY;
        fixtureDef2.filter.maskBits = WORLD_ENTITY;

        body.createFixture(fixtureDef);
        body2.createFixture(fixtureDef2);

        shape.dispose();

        // Now the physics body of the bottom edge of the screen
        BodyDef bodyDef3 = new BodyDef();
        bodyDef3.type = BodyDef.BodyType.StaticBody;

        float w = Gdx.graphics.getWidth()/PIXELS_TO_METERS;
        float h = Gdx.graphics.getHeight()/PIXELS_TO_METERS;

        bodyDef3.position.set(0,0);
        FixtureDef fixtureDef3 = new FixtureDef();
        fixtureDef3.filter.categoryBits = WORLD_ENTITY;
        fixtureDef3.filter.maskBits = PHYSICS_ENTITY;

        EdgeShape edgeShape = new EdgeShape();
        edgeShape.set(-w/2,-h/2,w/2,-h/2);
        fixtureDef3.shape = edgeShape;


        bodyEdgeScreen = world.createBody(bodyDef3);
        bodyEdgeScreen.createFixture(fixtureDef3);
        edgeShape.dispose();

        camera = new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.
                getHeight());
    }

    @Override
    public void render() {
        camera.update();
        // Step the physics simulation forward at a rate of 60hz
        world.step(1f/60f, 6, 2);

        sprite.setPosition((body.getPosition().x * PIXELS_TO_METERS) - sprite.
                        getWidth()/2 ,
                (body.getPosition().y * PIXELS_TO_METERS) -sprite.getHeight()/2 );


        sprite.setRotation((float)Math.toDegrees(body2.getAngle()));
        sprite2.setPosition((body2.getPosition().x * PIXELS_TO_METERS) - sprite2.
                        getWidth()/2 ,
                (body2.getPosition().y * PIXELS_TO_METERS) -sprite2.getHeight()/2 );
        sprite2.setRotation((float)Math.toDegrees(body.getAngle()));

        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(camera.combined);
        batch.begin();

        batch.draw(sprite, sprite.getX(), sprite.getY(),sprite.getOriginX(),
                sprite.getOriginY(),
                sprite.getWidth(),sprite.getHeight(),sprite.getScaleX(),sprite.
                        getScaleY(),sprite.getRotation());
        batch.draw(sprite2, sprite2.getX(), sprite2.getY(),sprite2.getOriginX(),
                sprite2.getOriginY(),
                sprite2.getWidth(),sprite2.getHeight(),sprite2.getScaleX(),sprite2.
                        getScaleY(),sprite2.getRotation());
        batch.end();
    }

    @Override
    public void dispose() {
        img.dispose();
        world.dispose();
    }
}

 

When a collision occurs, the collider will check it’s categoryBits against the collided’s maskBits.  If both are true, a collision occurs.  So in this example, both sprites will fall, completely ignoring each other.  However when they run in to the WORLD_ENTITY, a collision will occur.

 

Now, taking the same scenario, if you wanted to have the sprites collide with each other AND WORLD_ENTITY objects, you would simply change to

fixtureDef.filter.maskBits = WORLD_ENTITY|PHYSICS_ENTITY;

 

Now this object will have collisions with either kind of entity.

There is one other kind of filter in Box2D, and it's confusing as hell in my opinion. It's basically an override for grouping entities together called groupIndex.  I will let someone else explain how it works:

 

When checking two fixtures to see if they should collide:

  • if either fixture has a groupIndex of zero, use the category/mask rules as above
  • if both groupIndex values are non-zero but different, use the category/mask rules as above
  • if both groupIndex values are the same and positive, collide
  • if both groupIndex values are the same and negative, don't collide

 

The default value for the groupIndex is zero

 

So, if you want to have fine tune control over which entities interact with each other, Filter is how you do it.  Ok, let’s keep this part short and sweet.

Programming , ,

25. September 2014

 

So far we’ve covered creating a simple simulation then how to create physics bodies and apply forces to them now we look at dealing with collisions.  Truth of the matter is, most of the work is just done for us.  Let’s just right in with an example, heavily copied and pasted from the previous example.

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;

public class Physics2 extends ApplicationAdapter implements InputProcessor {
    SpriteBatch batch;
    Sprite sprite;
    Texture img;
    World world;
    Body body;
    Body bodyEdgeScreen;
    Box2DDebugRenderer debugRenderer;
    Matrix4 debugMatrix;
    OrthographicCamera camera;
    BitmapFont font;


    float torque = 0.0f;
    boolean drawSprite = true;

    final float PIXELS_TO_METERS = 100f;

    @Override
    public void create() {

        batch = new SpriteBatch();
        img = new Texture("badlogic.jpg");
        sprite = new Sprite(img);

        sprite.setPosition(-sprite.getWidth()/2,-sprite.getHeight()/2);

        world = new World(new Vector2(0, -1f),true);

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set((sprite.getX() + sprite.getWidth()/2) /
                        PIXELS_TO_METERS,
                (sprite.getY() + sprite.getHeight()/2) / PIXELS_TO_METERS);

        body = world.createBody(bodyDef);

        PolygonShape shape = new PolygonShape();
        shape.setAsBox(sprite.getWidth()/2 / PIXELS_TO_METERS, sprite.getHeight()
                /2 / PIXELS_TO_METERS);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = 0.1f;
        fixtureDef.restitution = 0.5f;

        body.createFixture(fixtureDef);
        shape.dispose();

        BodyDef bodyDef2 = new BodyDef();
        bodyDef2.type = BodyDef.BodyType.StaticBody;
        float w = Gdx.graphics.getWidth()/PIXELS_TO_METERS;
        // Set the height to just 50 pixels above the bottom of the screen so we can see the edge in the
        // debug renderer
        float h = Gdx.graphics.getHeight()/PIXELS_TO_METERS- 50/PIXELS_TO_METERS;
        //bodyDef2.position.set(0,
//                h-10/PIXELS_TO_METERS);
        bodyDef2.position.set(0,0);
        FixtureDef fixtureDef2 = new FixtureDef();

        EdgeShape edgeShape = new EdgeShape();
        edgeShape.set(-w/2,-h/2,w/2,-h/2);
        fixtureDef2.shape = edgeShape;

        bodyEdgeScreen = world.createBody(bodyDef2);
        bodyEdgeScreen.createFixture(fixtureDef2);
        edgeShape.dispose();

        Gdx.input.setInputProcessor(this);

        debugRenderer = new Box2DDebugRenderer();
        font = new BitmapFont();
        font.setColor(Color.BLACK);
        camera = new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.
                getHeight());
    }

    private float elapsed = 0;
    @Override
    public void render() {
        camera.update();
        // Step the physics simulation forward at a rate of 60hz
        world.step(1f/60f, 6, 2);

        body.applyTorque(torque,true);

        sprite.setPosition((body.getPosition().x * PIXELS_TO_METERS) - sprite.
                        getWidth()/2 ,
                (body.getPosition().y * PIXELS_TO_METERS) -sprite.getHeight()/2 )
        ;
        sprite.setRotation((float)Math.toDegrees(body.getAngle()));

        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(camera.combined);
        debugMatrix = batch.getProjectionMatrix().cpy().scale(PIXELS_TO_METERS,
                PIXELS_TO_METERS, 0);
        batch.begin();

        if(drawSprite)
            batch.draw(sprite, sprite.getX(), sprite.getY(),sprite.getOriginX(),
                    sprite.getOriginY(),
                    sprite.getWidth(),sprite.getHeight(),sprite.getScaleX(),sprite.
                            getScaleY(),sprite.getRotation());

        font.draw(batch,
                "Restitution: " + body.getFixtureList().first().getRestitution(),
                -Gdx.graphics.getWidth()/2,
               Gdx.graphics.getHeight()/2 );
        batch.end();

        debugRenderer.render(world, debugMatrix);
    }

    @Override
    public void dispose() {
        img.dispose();
        world.dispose();
    }

    @Override
    public boolean keyDown(int keycode) {
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {


        if(keycode == Input.Keys.RIGHT)
            body.setLinearVelocity(1f, 0f);
        if(keycode == Input.Keys.LEFT)
            body.setLinearVelocity(-1f,0f);

        if(keycode == Input.Keys.UP)
            body.applyForceToCenter(0f,10f,true);
        if(keycode == Input.Keys.DOWN)
            body.applyForceToCenter(0f, -10f, true);

        // On brackets ( [ ] ) apply torque, either clock or counterclockwise
        if(keycode == Input.Keys.RIGHT_BRACKET)
            torque += 0.1f;
        if(keycode == Input.Keys.LEFT_BRACKET)
            torque -= 0.1f;

        // Remove the torque using backslash /
        if(keycode == Input.Keys.BACKSLASH)
            torque = 0.0f;

        // If user hits spacebar, reset everything back to normal
        if(keycode == Input.Keys.SPACE|| keycode == Input.Keys.NUM_2) {
            body.setLinearVelocity(0f, 0f);
            body.setAngularVelocity(0f);
            torque = 0f;
            sprite.setPosition(0f,0f);
            body.setTransform(0f,0f,0f);
        }

        if(keycode == Input.Keys.COMMA) {
            body.getFixtureList().first().setRestitution(body.getFixtureList().first().getRestitution()-0.1f);
        }
        if(keycode == Input.Keys.PERIOD) {
            body.getFixtureList().first().setRestitution(body.getFixtureList().first().getRestitution()+0.1f);
        }
        if(keycode == Input.Keys.ESCAPE || keycode == Input.Keys.NUM_1)
            drawSprite = !drawSprite;

        return true;
    }

    @Override
    public boolean keyTyped(char character) {
        return false;
    }


    // On touch we apply force from the direction of the users touch.
    // This could result in the object "spinning"
    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        body.applyForce(1f,1f,screenX,screenY,true);
        //body.applyTorque(0.4f,true);
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        return false;
    }
}

 

Here is the application running

Click to open in new Window (It's probably already run by the time you scrolled down)


You can control it using the following controls:

  • Left and Right Arrow:  Apply impulse along X axis
  • Up and Down Arrow: Apply force along Y axis.
  • [ and ]: Apply torque
  • \: Set torque to 0
  • SPACEBAR or '2': Reset all
  • Click, apply force from point of click
  • ESC or '1' toggles display of the sprite graphic on and off

 

This is basically the same application we created in the previous example, except now there is actually something to collide with.  In this case we created a physics body defined by an edge shape just 50 pixels above the bottom of the screen.  The key difference between our two bodies is bodyEdgeScreen has it’s physics type set to StaticBody.  A static body is exactly what it’s name implies, static, meaning that it doesn’t move.  It participates in the physics simulation ( stuff can hit it ), but it is un affected by the simulation itself.  If this body wasn’t static, gravity would have grabbed it and set it free falling in space.  As you can see, it is created the same way as our other physics body.  The only other major difference is instead of being defined in the physics engine by a box, it is instead represented as an edge defined by two points.  This means our EdgeShape has no volume.

 

Now that we have collisions occurring a few values became much more important, density, restitution (and not shown, friction).  These are set on the FixtureDef of your Body.  We touched briefly on density in the previous tutorials, it helps determine the overall mass of your object along with the shape.  Restitution can be thought of as, for the lack of a better term, as bounciness.  Restitution helps determine how one body will respond to contact with another physics object.  Rubber for example would have a high restitution, while rock would have a very low restitution.  Friction on the other hand determines how one body slides across another body.

 

What happens if you want to perform some kind of operation when a collision occurs?  Well fortunately this is quite simple, let’s take a look at another heavily cut and paste example!

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;

public class Physics3 extends ApplicationAdapter {
    SpriteBatch batch;
    Sprite sprite,sprite2;
    Texture img;
    World world;
    Body body,body2;
    Body bodyEdgeScreen;

    Matrix4 debugMatrix;
    OrthographicCamera camera;

    final float PIXELS_TO_METERS = 100f;

    @Override
    public void create() {
        batch = new SpriteBatch();
        img = new Texture("badlogic.jpg");

        // Create two identical sprites slightly offset from each other vertically
        sprite = new Sprite(img);
        sprite.setPosition(-sprite.getWidth()/2,-sprite.getHeight()/2 +200);
        sprite2 = new Sprite(img);
        sprite2.setPosition(-sprite.getWidth()/2 + 20,-sprite.getHeight()/2 + 400);

        world = new World(new Vector2(0, -1f),true);

        // Sprite1's Physics body
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set((sprite.getX() + sprite.getWidth()/2) /
                        PIXELS_TO_METERS,
                (sprite.getY() + sprite.getHeight()/2) / PIXELS_TO_METERS);


        body = world.createBody(bodyDef);

        // Sprite2's physics body
        BodyDef bodyDef2 = new BodyDef();
        bodyDef2.type = BodyDef.BodyType.DynamicBody;
        bodyDef2.position.set((sprite2.getX() + sprite2.getWidth()/2) /
                        PIXELS_TO_METERS,
                (sprite2.getY() + sprite2.getHeight()/2) / PIXELS_TO_METERS);

        body2 = world.createBody(bodyDef2);

        // Both bodies have identical shape
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(sprite.getWidth()/2 / PIXELS_TO_METERS, sprite.getHeight()
                /2 / PIXELS_TO_METERS);

        // Sprite1
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.density = 0.1f;
        fixtureDef.restitution = 0.5f;


        // Sprite2
        FixtureDef fixtureDef2 = new FixtureDef();
        fixtureDef2.shape = shape;
        fixtureDef2.density = 0.1f;
        fixtureDef2.restitution = 0.5f;

        body.createFixture(fixtureDef);
        body2.createFixture(fixtureDef2);

        shape.dispose();

        // Now the physics body of the bottom edge of the screen
        BodyDef bodyDef3 = new BodyDef();
        bodyDef3.type = BodyDef.BodyType.StaticBody;
        float w = Gdx.graphics.getWidth()/PIXELS_TO_METERS;
        float h = Gdx.graphics.getHeight()/PIXELS_TO_METERS;

        bodyDef3.position.set(0,0);
        FixtureDef fixtureDef3 = new FixtureDef();

        EdgeShape edgeShape = new EdgeShape();
        edgeShape.set(-w/2,-h/2,w/2,-h/2);
        fixtureDef3.shape = edgeShape;


        bodyEdgeScreen = world.createBody(bodyDef3);
        bodyEdgeScreen.createFixture(fixtureDef3);
        edgeShape.dispose();

        camera = new OrthographicCamera(Gdx.graphics.getWidth(),Gdx.graphics.
                getHeight());

        world.setContactListener(new ContactListener() {
            @Override
            public void beginContact(Contact contact) {
                // Check to see if the collision is between the second sprite and the bottom of the screen
                // If so apply a random amount of upward force to both objects... just because
                if((contact.getFixtureA().getBody() == bodyEdgeScreen &&
                        contact.getFixtureB().getBody() == body2)
                        ||
                        (contact.getFixtureA().getBody() == body2 &&
                                contact.getFixtureB().getBody() == bodyEdgeScreen)) {

                    body.applyForceToCenter(0,MathUtils.random(20,50),true);
                    body2.applyForceToCenter(0, MathUtils.random(20,50), true);
                }
            }

            @Override
            public void endContact(Contact contact) {
            }

            @Override
            public void preSolve(Contact contact, Manifold oldManifold) {
            }

            @Override
            public void postSolve(Contact contact, ContactImpulse impulse) {
            }
        });
    }

    @Override
    public void render() {
        camera.update();
        // Step the physics simulation forward at a rate of 60hz
        world.step(1f/60f, 6, 2);

        sprite.setPosition((body.getPosition().x * PIXELS_TO_METERS) - sprite.
                        getWidth()/2 ,
                (body.getPosition().y * PIXELS_TO_METERS) -sprite.getHeight()/2 );


        sprite.setRotation((float)Math.toDegrees(body2.getAngle()));
        sprite2.setPosition((body2.getPosition().x * PIXELS_TO_METERS) - sprite2.
                        getWidth()/2 ,
                (body2.getPosition().y * PIXELS_TO_METERS) -sprite2.getHeight()/2 );
        sprite2.setRotation((float)Math.toDegrees(body.getAngle()));

        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(camera.combined);
        batch.begin();

        batch.draw(sprite, sprite.getX(), sprite.getY(),sprite.getOriginX(),
                sprite.getOriginY(),
                sprite.getWidth(),sprite.getHeight(),sprite.getScaleX(),sprite.
                        getScaleY(),sprite.getRotation());
        batch.draw(sprite2, sprite2.getX(), sprite2.getY(),sprite2.getOriginX(),
                sprite2.getOriginY(),
                sprite2.getWidth(),sprite2.getHeight(),sprite2.getScaleX(),sprite2.
                        getScaleY(),sprite2.getRotation());
        batch.end();
    }

    @Override
    public void dispose() {
        img.dispose();
        world.dispose();
    }
}

 

And when you run it

 

Click to open in new Window (It's probably already run by the time you scrolled down)


 

In this example we’ve added another dynamic physics body.  Both bodies fall thanks to gravity, however when the second physics body makes contact with the “ground”, an upward force is applied to both bodies.

 

The heart of this example is the ContactListener we added to our physics world.  This handler is called for each collision that occurred in the world and allows you to handle the event before and after contact, then before and after the physics engine does the calculations on what to do.  In this example we used the beginContact callback, although truth is, all 4 would have behaved identically.  In each case a Contact object is passed in, which has details on the collision such as the friction, restitution and speed of the collision.  In this case we actually want to check what types of objects collided, which we do using getFixtureA() and getFixtureB() which returns the FixtureDef of the colliding objects.  Since we don’t actually know which collides with which, we test for both directions of collisions.  If the bodies colliding are Body2 and bodyEdgeScreen, we apply our upward force.  Obviously you could handle whatever kind of calculation you wanted at this point.

 

One very common activity, and something I didn’t cover in this example is tying a physics body back to the object in the real world that this body represents.  In this particular example we’ve simply hard coded things, but in a real life application this isn’t an option.  Fortunately there is an easy elegant solution.  Each physics Body has a property called UserData, which can be basically whatever you want it to be.  Let’s take a quick look at storing the game sprite using UserData of the physics Body.

        //Create the body
        body = world.createBody(bodyDef);
        //Store the sprite the body represents in UserData
        body.setUserData(sprite);
        //Access the sprite
        ((Sprite)body.getUserData()).setPosition(body.getPosition().x,body.getPosition().y);

 

This provides an easy way to link the object represented with the physics entity itself.

 

So far we covered dynamic objects, which can be moved and positioned and participate in the physics simulation, and static bodies, that do not move but can participate in the simulation, what what happens if you want a body to be aware of the physics simulation, but not effect it.  For example, what if you wanted to trigger some code when a physics body went off screen, but you didn’t want this to cause a collision.  There is a very simple solution to this, sensors.

        fixtureDef.isSensor = true;

 

This is all you need to do an now your physics object will no longer cause a reaction when collided with, except whatever you provide in code.  For example, in the earlier example, if you changed fixtureDef3 to be a sensor, the first body will simply fall through it.  However when the second body hits it, it will trigger the code in our ContactListener.

 

If you thought to yourself, wow, that’s an ugly way to handle tests for collisions between different objects… that whole test in both directions thing will get awfully ugly quickly.  You are 100% correct, fortunately there is a clean solution I will cover shortly.

Programming , ,

Month List

Popular Comments