LibGDX Displaying UI elements in game world

180 views Asked by At

I'm making a game in LibGDX. I use Box2d to manage my game world. I'm thinking of a way I can display things like a health bar or nickname related to the body. I'm currently converting world coordinates to screen coordinates using the project() method to move elements around in Stage accordingly. But there's a problem with this - I'd like elements to be visible as objects in the game world, not the UI. I mean taking into account camera zoom, scaling, etc.

For example, let's say I want to display a Label over a body. I can do this by converting the appropriate coordinates and assigning them to labels, but that doesn't scale the text to match the parameters of the camera. How can I get the desired effect?

1

There are 1 answers

1
bornander On

Rendering things like text in the world-view can be done by moving the position of Labels that are not added to any stage, simply call the draw method directly on the Label and pass a SpriteBatch that has a projection matrix set from the world camera.

You can also forgo using Labels all together and "manually" render the text using direct draw-calls, this gives you a greater level of control at the cost of having to write a bit more code (I would do it this way). The BitmapFonts draw method will be the basic way of doing this.enter image description here

In the example below I am using both techniques to display some text that appears next to a Box2D body and it is also affected by the zooming in that the camera does:

The full source code for the example is below (it uses the default font from the libGDX examples);

package com.bornander.sandbox;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.utils.ScreenUtils;
    
public class MyGdxSandbox extends ApplicationAdapter {
    World world;
    OrthographicCamera worldCamera;
    SpriteBatch spriteBatch;
    BitmapFont font;
    Box2DDebugRenderer box2DDebugRenderer;
    Body ballBody;
    Label ballLabel;

    @Override
    public void create () {
        world = new World(new Vector2(0.0f, -10.0f), false);
        float aspectRatio = (float)Gdx.graphics.getWidth() / Gdx.graphics.getHeight();
        float worldCameraViewportWidth = 100.0f;
        worldCamera = new OrthographicCamera(worldCameraViewportWidth, worldCameraViewportWidth / aspectRatio);
        worldCamera.position.set(worldCamera.viewportWidth / 2.0f, worldCamera.viewportHeight / 2.0f, 1.0f);
        worldCamera.zoom = 3.0f;
        spriteBatch = new SpriteBatch();
        font = new BitmapFont(Gdx.files.internal("default.fnt"));
        font.setUseIntegerPositions(false);
        box2DDebugRenderer = new Box2DDebugRenderer();

        CircleShape ballShape = new CircleShape();
        ballShape.setRadius(4.0f);

        FixtureDef ballFixtureDef = new FixtureDef();
        ballFixtureDef.shape = ballShape;
        ballFixtureDef.friction = 0.2f;
        ballFixtureDef.density = 1.0f;
        ballFixtureDef.restitution = 0.65f;

        BodyDef ballBodyDef = new BodyDef();
        ballBodyDef.type = BodyDef.BodyType.DynamicBody;

        ballBody = world.createBody(ballBodyDef);
        ballBody.createFixture(ballFixtureDef);
        ballBody.setTransform(25, 50, 0);
        ballBody.applyLinearImpulse(200, 0, ballBody.getPosition().x, ballBody.getPosition().y, true);

        PolygonShape groundShape = new PolygonShape();
        groundShape.setAsBox(25, 2);

        FixtureDef groundFixtureDef = new FixtureDef();
        groundFixtureDef.shape = groundShape;
        groundFixtureDef.friction = 0.2f;
        groundFixtureDef.density = 1.0f;
        groundFixtureDef.restitution = 0.2f;

        BodyDef groundBodyDef = new BodyDef();
        groundBodyDef.type = BodyDef.BodyType.StaticBody;

        Body ground = world.createBody(groundBodyDef);
        ground.createFixture(groundFixtureDef);
        ground.setTransform(50, 20, 0);

        // This label is never attached to a Stage, so things like click handlers are not enabled
        ballLabel = new Label("The label", new Label.LabelStyle(font, Color.WHITE));
    }

    @Override
    public void render () {
        worldCamera.zoom -= Gdx.graphics.getDeltaTime() * 0.3f;
        ScreenUtils.clear(0, 0, 0, 1);
        float delta = Gdx.graphics.getDeltaTime();
        worldCamera.update();
        world.step(delta, 8, 8);
        Vector2 ballPositionWorld = ballBody.getPosition();


        spriteBatch.setProjectionMatrix(worldCamera.combined);
        spriteBatch.begin();
        // You can render the Label directly, not using a stage
        ballLabel.setPosition(ballPositionWorld.x, ballPositionWorld.y);

        // Or you cam render the text "manually"
        font.draw(spriteBatch, "The ball", ballPositionWorld.x, ballPositionWorld.y);
        ballLabel.draw(spriteBatch, 1.0f);
        spriteBatch.end();

        box2DDebugRenderer.render(world, worldCamera.combined);
    }
}