“Libgdx cookbook” review

Since we are heavily relying on the amazing game development framework libGDX, we will post a short review about the “Libgdx Cross-platform Game Development Cookbook” by David Saltares Márquez and Alberto Cejas Sánchez which has lately been released.

Libgdx Cross-platform Game Development Cookbook

General

Being a “cookbook” it is structured in small chapters (socalled recipes), that may be read or looked up individually. This is particularly handy, if you are no beginner, but have a good understanding of most topics and just want to read through some certain chapters.

Unlike a lot of other sources about libGDX, this book is up to date! For example, it covers the gradle build system to build your libGDX projects, as well as Viewports to handle different resolutions and aspect ratios. Both features were introduced in early 2014.

A negative aspect: When reading it, the book looks a bit cluttered. The many different ways of formatting on a single page make it a bit difficult to read. However, that might be because of the high degree of “compactness” of this book. It jumps directly into action, delivering a constant stream of how tos, hints, warnings and facts in the form of code listings, notes, lists (and many others…).

Content

It delivers hands on practical tips about common problems that might happen, even when it’s just about the tools, like the IDE, you are using. The experience of the authors working with libGDX is really noticable.

Furthermore it covers some topics, like git and gradle, that are not directly related to libGDX, but will tremendously help using it and working with it, especially if you are planning to contribute to libGDX yourself.

The book also describes commonly used tools like the 2D Particle Editor, Texture Packer (GUI), Hiero, Gradle Setup UI etc. that come with libGDX. Those will help with a basic game development workflow.

Another slightly negative aspect: It covers some basic information that other sources like the wiki also cover. One example is the File API or the Box2D API. That makes the book more suitable for beginners as a single source of information to get started with LibGDX. However it results in some advanced topics being missing, like the 3D API which certainly would have been worth a chapter or even two.

The chapter “Third party Libraries and Extras” partly makes up for that though. It introduces some tools, extensions or related frameworks and libraries that go well with libGDX and are not part of the framework core.

Conclusion

In total, it is worth buying it, especially if you are a beginner. But even for advanced libGDX developers this book will still be useful and reveal some unknown secrets, tips and tricks about game development with libGDX. You can buy it here.

Precompiled, runnable libGDX tools

Since not everyone has easy access to the libGDX sources and is able to build or run the tools that one needs, we’ll keep a list of the most important libGDX tools here in form of a runnable / precompiled .jar file.

To run these, just double click the file (should work on most operating systems), or start it via the command line:

java -jar <tool>.jar

Last update: 31 July 2014 (commit)

Quick solution for 3D culling in libGDX

By default, libGDX does not perform any kind of culling when rendering models via the ModelBatch. This blog post by @xoppa shows how it can be implemented with game logic specific knowledge. That means it is not generic and can make use game specific information, because the models will be culled before they actually reach the ModelBatch.

However, we have tried a more generic approach by extending ModelBatch and implementing culling right here and there. The following CullingModelBatch is optimized for speed, but also has some limitations:

public class CullingModelBatch extends ModelBatch {

    private ObjectMap<Mesh, Float> radiuses = new ObjectMap<Mesh, Float>();

    private Vector3 tmp = new Vector3();

    @Override
    public void flush() {
        Iterator<Renderable> iter = renderables.iterator();

        while (iter.hasNext()) {
            Renderable renderable = iter.next();
            renderable.worldTransform.getTranslation(tmp);
            if (!camera.frustum.sphereInFrustumWithoutNearFar(tmp, getRadiusOfMesh(renderable.mesh))) {
                iter.remove();
            }
        }
        super.flush();
    }

    private float getRadiusOfMesh(Mesh mesh) {
        Float radius = radiuses.get(mesh);
        if (radius != null) {
            return radius;
        }

        Vector3 dimensions = mesh.calculateBoundingBox().getDimensions();
        radius = Math.max(Math.max(dimensions.x, dimensions.y), dimensions.z);

        radiuses.put(mesh, radius);
        return radius;
    }

}

Pros:

  • Easy to activate culling just by replacing your standard batch with this one
  • Lightning fast
    • Because of the sphereInFrustumWithoutNearFar test
    • Because of the caching mechanism
    • Because of the Mesh check instead of the MeshPart check

Cons:

  • Does not work with animations
  • Can produce false positives (not everything that could be culled, will be culled)
    • Because of the sphere check
    • Because we check the whole Mesh and not the rendered MeshPart
  • Small memory footprint

The memory overhead of this approach might very well be neglected. In total it is a trade-off between CPU processing time and GPU rendering time. To get more accurate results one might replace the sphere check with a bounding box check, or check the actual MeshPart of the renderable instead of the complete Mesh.

This apprach might not produce the optimal results, but it is a very quick solution which you can try just by replacing new ModelBatch() with new CullingModelBatch() in your game. In many games this might very well be sufficient because it does get rid of a lot of models very quickly and in our upcoming game Pocket Galaxy it was completely sufficient.

LibGDX Flame (3D Particle Editor) binaries

Yesterday the libGDX pull request 2005 has been merged into the core. It contained a whole new 3D particle engine made by @lordjone.

There is no wiki page yet, but that will change soon. For now you can see how to use it in the ParticleControllerTest. The result of that looks like this:

Preview of the ParticleControllerTest

Beautiful! Feel free to grab the editor from below, try it yourself and share your effects!

The editor

It did not only contain the 3D particle engine itself though, but even an editor to create and preview different particle effects. A runnable JAR file of the editor called Flame can be downloaded here. It can either be run via a double-click on the file in most operating systems, or on the command-line via the following:

java -jar flame.jar

Here you can see a screenshot of it in action:

Preview of the FlameEditor

Drag and Drop Inventory with LibGDX (Part IV)

In this last part of our series on an inventory we are going to add the finishing touch, namely tooltips.

The SlotTooltip

The SlotTooltip is a Window itself. In contrast to the inventory, it does not have a button to close. Furthermore it displays the amount of items and the item name in the title bar. The content area could display anything, but to keep it simple, it will be just another dummy Label with some text. It’s also a SlotListener and will get notified when a slot changes. According to the new state of the slot, the tooltip will hide itself when the slot is empty.

public class SlotTooltip extends Window implements SlotListener {

    private Skin skin;

    private Slot slot;

    public SlotTooltip(Slot slot, Skin skin) {
        super("Tooltip...", skin);
        this.slot = slot;
        this.skin = skin;
        hasChanged(slot);
        slot.addListener(this);
        setVisible(false);
    }

    @Override
    public void hasChanged(Slot slot) {
        if (slot.isEmpty()) {
            setVisible(false);
            return;
        }

        // title displays the amount
        setTitle(slot.getAmount() + "x " + slot.getItem());
        clear();
        Label label = new Label("Super awesome description of " + slot.getItem(), skin);
        add(label);
        pack();
    }

    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        // the listener sets this to true in case the slot is hovered
        // however, we don't want that in case the slot is empty
        if (slot.isEmpty()) {
            super.setVisible(false);
        }
    }

}

Making the tooltip follow the cursor

The following listener will be attached to every SlotActor and will activate or disable the tooltip when the mouse hovers over a certain slot. It will also hide the tooltip when the mouse is not hovering anymore. Furthermore it adjusts the tooltip’s position based on the movement of the mouse cursor.

public class TooltipListener extends InputListener {

    private boolean inside;

    private Actor tooltip;
    private boolean followCursor;

    private Vector2 position = new Vector2();
    private Vector2 tmp = new Vector2();
    private Vector2 offset = new Vector2(10, 10);

    public TooltipListener(Actor tooltip, boolean followCursor) {
        this.tooltip = tooltip;
        this.followCursor = followCursor;
    }

    @Override
    public boolean mouseMoved(InputEvent event, float x, float y) {
        if (inside && followCursor) {
            event.getListenerActor().localToStageCoordinates(tmp.set(x, y));
            tooltip.setPosition(tmp.x + position.x + offset.x, tmp.y + position.y + offset.y);
        }
        return false;
    }

    @Override
    public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
        inside = true;
        tooltip.setVisible(true);
        tmp.set(x, y);
        event.getListenerActor().localToStageCoordinates(tmp);
        tooltip.setPosition(tmp.x + position.x + offset.x, tmp.y + position.y + offset.y);
        tooltip.toFront();
    }

    @Override
    public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
        inside = false;
        tooltip.setVisible(false);
    }

    /**
     * The offset of the tooltip from the touch position. It should not be
     * positive as the tooltip will flicker otherwise.
     */
    public void setOffset(float offsetX, float offsetY) {
        offset.set(offsetX, offsetY);
    }

}

Usage

Attaching a tooltip to a slot is really easy now. All it takes is adding a new SlotTooltip to the stage and registering the TooltipListener.

public SlotActor(Skin skin, Slot slot) {
    ....
    SlotTooltip tooltip = new SlotTooltip(slot, skin);
    InventoryScreen.stage.addActor(tooltip);
    addListener(new TooltipListener(tooltip, true));
}

The end

Finally, that’s it. We’ve created an interactive inventory with all sorts of features. For the full code see our public GitHub repository.

Drag and Drop Inventory with LibGDX (Part III)

Compared to the other parts of this series, this one will contain less code, but will add a lot of functionality! We will make the inventory interactive by making the items draggable.

The DragAndDrop class

Drag and drop functionality is usually always avoided since it is very difficult to implement. LibGDX on the other hand provides a neat little class to handle it: DragAndDrop. Unfortunately there is not much documentation about it besides the JavaDoc and this test.

Basically a DragAndDrop handles all you need for the input processing like hovering, clicking and dragging. All one has to do, is define Targets and Sources. A Source can define a Payload, which can be any Object. In our case this will be a Slot.

The SlotSource

With the above description, understanding the following code should be a piece of cake!

public class SlotSource extends Source {

    private Slot sourceSlot;

    public SlotSource(SlotActor actor) {
        super(actor);
        this.sourceSlot = actor.getSlot();
    }

    @Override
    public Payload dragStart(InputEvent event, float x, float y, int pointer) {
        if (sourceSlot.getAmount() == 0) {
            return null;
        }

        Payload payload = new Payload();
        Slot payloadSlot = new Slot(sourceSlot.getItem(), sourceSlot.getAmount());
        sourceSlot.take(sourceSlot.getAmount());
        payload.setObject(payloadSlot);

        TextureAtlas icons = LibgdxUtils.assets.get("icons/icons.atlas", TextureAtlas.class);
        TextureRegion icon = icons.findRegion(payloadSlot.getItem().getTextureRegion());

        Actor dragActor = new Image(icon);
        payload.setDragActor(dragActor);

        Actor validDragActor = new Image(icon);
        payload.setValidDragActor(validDragActor);

        Actor invalidDragActor = new Image(icon);
        payload.setInvalidDragActor(invalidDragActor);

        return payload;
    }

    @Override
    public void dragStop(InputEvent event, float x, float y, int pointer, Payload payload, Target target) {
        Slot payloadSlot = (Slot) payload.getObject();
        // the payload was dropped over a valid target
        if (target != null) {
            Slot targetSlot = ((SlotActor) target.getActor()).getSlot();
            // if the item is the same, stack it
            if (targetSlot.getItem() == payloadSlot.getItem() || targetSlot.getItem() == null) {
                targetSlot.add(payloadSlot.getItem(), payloadSlot.getAmount());
            } else {
                // the item is not the same, thus switch the items
                Item targetType = targetSlot.getItem();
                int targetAmount = targetSlot.getAmount();
                targetSlot.take(targetAmount);
                targetSlot.add(payloadSlot.getItem(), payloadSlot.getAmount());
                sourceSlot.add(targetType, targetAmount);
            }
        } else {
            // the payload was not dropped over a target, thus put it back to where it came from
            sourceSlot.add(payloadSlot.getItem(), payloadSlot.getAmount());
        }
    }
}

The SlotTarget

The code for the target is even less complicated, since most of the logic is already handled by the SlotSource.

public class SlotTarget extends Target {

    private Slot targetSlot;

    public SlotTarget(SlotActor actor) {
        super(actor);
        targetSlot = actor.getSlot();
        getActor().setColor(Color.LIGHT_GRAY);
    }

    @Override
    public boolean drag(Source source, Payload payload, float x, float y, int pointer) {
        Slot payloadSlot = (Slot) payload.getObject();

        // in case we drag something over this target, we highlight it a bit
        getActor().setColor(Color.WHITE);

        // returning true means that this is a valid target
        return true;
    }

    @Override
    public void drop(Source source, Payload payload, float x, float y, int pointer) {
        // we already handle all of this in dragStop in the Source
    }

    @Override
    public void reset(Source source, Payload payload) {
        getActor().setColor(Color.LIGHT_GRAY);
    }

}

Putting it all together

For now we have just defined some classes, but we haven’t actually used them yet. In the previous parts of this series, there have already been some places where those classes were used, but it wasn’t clear yet what they did.

The following was found in the constructor of the InventoryActor and creates a SlotSource and a SlotTarget for every slot.

for (Slot slot : inventory.getSlots()) {
    SlotActor slotActor = new SlotActor(skin, slot);
    dragAndDrop.addSource(new SlotSource(slotActor));
    dragAndDrop.addTarget(new SlotTarget(slotActor));
    add(slotActor);
    ...
}

And the InventoryActor got the DragAndDrop supplied by the InventoryScreen:

DragAndDrop dragAndDrop = new DragAndDrop();
inventoryActor = new InventoryActor(new Inventory(), dragAndDrop, skin);

That’s it already! Everything else is handled by the sources, targets and DragAndDrop.

Next

In Part IV we will add the last finishing touch. Tooltips!

Drag and Drop Inventory with LibGDX (Part II)

In this part II of our inventory tutorial series we are going to create the actual UI for our Inventory and Slots. LibGDX offers us some basic functionality for UIs, namely scene2d.ui. In case you are not familiar with it yet, you should probably read this article first.

Again: the full source code can be found here.

The SlotActor

In part I of this series, we have seen how a Slot looks like. Now we have to render this slot somehow. To do so, we are going to create a SlotActor which is an ImageButton itself, and which displays the TextureRegion of the Item.

public class SlotActor extends ImageButton implements SlotListener {

    private Slot slot;

    private Skin skin;

    public SlotActor(Skin skin, Slot slot) {
        super(createStyle(skin, slot));
        this.slot = slot;
        this.skin = skin;

        // this actor has to be notified when the slot itself changes
        slot.addListener(this);

        // ignore this for now, it will be explained in part IV
        SlotTooltip tooltip = new SlotTooltip(slot, skin);
        InventoryScreen.stage.addActor(tooltip);
        addListener(new TooltipListener(tooltip, true));
    }

    /**
     * This will create a new style for our image button, with the correct image for the item type.
     */
    private static ImageButtonStyle createStyle(Skin skin, Slot slot) {
        TextureAtlas icons = LibgdxUtils.assets.get("icons/icons.atlas", TextureAtlas.class);
        TextureRegion image;
        if (slot.getItem() != null) {
            image = icons.findRegion(slot.getItem().getTextureRegion());
        } else {
            // we have a special "empty" region in our atlas file, which is just black
            image = icons.findRegion("nothing");
        }
        ImageButtonStyle style = new ImageButtonStyle(skin.get(ButtonStyle.class));
        style.imageUp = new TextureRegionDrawable(image);
        style.imageDown = new TextureRegionDrawable(image);

        return style;
    }

    @Override
    public void hasChanged(Slot slot) {
        // when the slot changes, we switch the icon via a new style
        setStyle(createStyle(skin, slot));
    }

    public Slot getSlot() {
        return slot;
    }

}

As always, read the code carefully and also check the comments within. It should be pretty self-explanatory.

The InventoryActor

To display the Inventory, we are going to create a new Window which can be dragged around by default. For each Slot of the Inventory, we are going to create a new SlotActor, which we’ve just seen in the previous paragraph.

public class InventoryActor extends Window {

    public InventoryActor(Inventory inventory, DragAndDrop dragAndDrop, Skin skin) {
        super("Inventory", skin);

        // add an "X" button to the top right of the window, and make it hide the inventory
        TextButton closeButton = new TextButton("X", skin);
        closeButton.addListener(new HidingClickListener(this));
        getButtonTable().add(closeButton).height(getPadTop());

        // basic layout
        setPosition(400, 100);
        defaults().space(8);
        row().fill().expandX();

        // run through all slots and create SlotActors for each
        int i = 0;
        for (Slot slot : inventory.getSlots()) {
            SlotActor slotActor = new SlotActor(skin, slot);
            add(slotActor);

            // this can be ignored for now and will be explained in part III
            dragAndDrop.addSource(new SlotSource(slotActor));
            dragAndDrop.addTarget(new SlotTarget(slotActor));

            i++;
            // every 5 cells, we are going to jump to a new row
            if (i % 5 == 0) {
                row();
            }
        }

        pack();

        // it is hidden by default
        setVisible(false);
    }
}

Closing the inventory

This is actually pretty easy. In the previous code you could already see that we added a HidingClickListener to the button to close the window. Its only purpose is to hide a given actor on click.

public class HidingClickListener extends ClickListener {

    private Actor actor;

    public HidingClickListener(Actor actor) {
        this.actor = actor;
    }

    @Override
    public void clicked(InputEvent event, float x, float y) {
        actor.setVisible(false);
    }

}

The inventory screen

Now that we’ve got the nessary Actors to display our UI, we actually need a Screen with a Stage to attach those actors to.

public class InventoryScreen implements Screen {

    private InventoryActor inventoryActor;

    public static Stage stage;

    @Override
    public void show() {
        // create the stage and make it receive all input
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        Skin skin = new Skin(Gdx.files.internal("skins/uiskin.json"));

        DragAndDrop dragAndDrop = new DragAndDrop();
        inventoryActor = new InventoryActor(new Inventory(), dragAndDrop, skin);
        stage.addActor(inventoryActor);
    }

    @Override
    public void render(float delta) {
        // the screen will have a dark grey background colour
        Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

        // show the inventory when any key is pressed
        if (Gdx.input.isKeyPressed(Input.Keys.ANY_KEY)) {
            inventoryActor.setVisible(true);
        }

        // handle all inputs and draw the whole UI
        stage.act(delta);
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
        stage.getViewport().update(width, height, true);
    }

    @Override
    public void hide() {
        Gdx.input.setInputProcessor(null);
        dispose();
    }

    @Override
    public void dispose() {
        stage.dispose();
    }

}

Next

In this part, we have created a basic UI for our inventory and slots. But we cannot do much with it yet. In the next part we will see how to make that inventory interactive by adding drag and drop functionality!

Drag and Drop Inventory with LibGDX (Part I)

This part of the inventory tutorial series is about the base classes to handle the logic, store the inventory information and manage them.

The Items

First we need to define what an Item is. For us it is just an enum. Additionally the enum has a String value which is the name of the TextureRegion in our TextureAtlas with all icon textures. The atlas files can be checked out here.

public enum Item {
    CRYSTAL_RED("redcrystal"), CRYSTAL_BLUE("bluecrystal"), CRYSTAL_GREEN("greencrystal"),
    CRYSTAL_YELLOW("yellowcrystal"), CRYSTAL_MAGENTA("magentacrystal"),
    CRYSTAL_CYAN("cyancrystal"), CRYSTAL_ORANGE("orangecrystal"), ...;

    private String textureRegion;

    private Item(String textureRegion) {
        this.textureRegion = textureRegion;
    }

    public String getTextureRegion() {
        return textureRegion;
    }
}

The slots

To support stacked items, our inventory Slots won’t only have an Item type, but also the number of items. The class itself is pretty simple as it only manages those two variables with an add(...) and take(...) method. What the SlotListener does is described in the next paragraph.

public class Slot {

    private Item item;

    private int amount;

    private Array<SlotListener> slotListeners = new Array<SlotListener>();

    public Slot(Item item, int amount) {
        this.item = item;
        this.amount = amount;
    }

    public boolean isEmpty() {
        return item == null || amount <= 0;
    }

    public boolean add(Item item, int amount) {
        if (this.item == item || this.item == null) {
            this.item = item;
            this.amount += amount;
            notifyListeners();
            return true;
        }

        return false;
    }

    public boolean take(int amount) {
        if (this.amount >= amount) {
            this.amount -= amount;
            if (this.amount == 0) {
                item = null;
            }
            notifyListeners();
            return true;
        }

        return false;
    }

    public void addListener(SlotListener slotListener) {
        slotListeners.add(slotListener);
    }

    public void removeListener(SlotListener slotListener) {
        slotListeners.removeValue(slotListener, true);
    }

    private void notifyListeners() {
        for (SlotListener slotListener : slotListeners) {
            slotListener.hasChanged(this);
        }
    }

    public Item getItem() {
        return item;
    }

    public int getAmount() {
        return amount;
    }

    @Override
    public String toString() {
        return "Slot[" + item + ":" + amount + "]";
    }
}

Furthermore we have a little interface called SlotListener which makes it possible to trigger some actions when the slot has changed.

public interface SlotListener {

    /**
    * Will be called whenever the slot has changed.
    */
    void hasChanged(Slot slot);

}    

The inventory

The Inventory manages a bunch of Slots. In this case we create 25 Slots and add a few random Items.

public class Inventory {

    private Array<Slot> slots;

    public Inventory() {
        slots = new Array<Slot>(25);
        for (int i = 0; i < 25; i++) {
            slots.add(new Slot(null, 0));
        }

        // create some random items
        for (Slot slot : slots) {
            slot.add(Item.values()[MathUtils.random(0, Item.values().length - 1)], 1);
        }

        // create a few random empty slots
        for (int i = 0; i < 3; i++) {
            Slot randomSlot = slots.get(MathUtils.random(0, slots.size - 1));
            randomSlot.take(randomSlot.getAmount());
        }
    }

    public int checkInventory(Item item) {
        int amount = 0;

        for (Slot slot : slots) {
            if (slot.getItem() == item) {
                amount += slot.getAmount();
            }
        }

        return amount;
    }

    public boolean store(Item item, int amount) {
        // first check for a slot with the same item type
        Slot itemSlot = firstSlotWithItem(item);
        if (itemSlot != null) {
            itemSlot.add(item, amount);
            return true;
        } else {
            // now check for an available empty slot
            Slot emptySlot = firstSlotWithItem(null);
            if (emptySlot != null) {
                emptySlot.add(item, amount);
                return true;
            }
        }

        // no slot to add
        return false;
    }

    public Array<Slot> getSlots() {
        return slots;
    }

    private Slot firstSlotWithItem(Item item) {
        for (Slot slot : slots) {
            if (slot.getItem() == item) {
                return slot;
            }
        }

        return null;
    }

}

Next

In Part II of this tutorial series, we are going to leverage this basic inventory system onto a scene2d.ui layer to actually display the inventory.

Drag and Drop Inventory with LibGDX

Many games are in need of a UI for an inventory. So does our game Pocket Galaxy and that’s why we thought to share our solution with you.

Our inventory basically offers the following features:

  • Inventory is placed in an extra window
  • Grid-based layout
  • Random amount of slots
  • Icons are displayed for the items
  • Items can stack
  • Tooltips with additional information
  • Items can be moved around via Drag and Drop
  • It’s possible to define valid and invalid targets

Especially the point about drag and drop might scare many people, since that’s usually a lot of work, but with LibGDX and its scene2d framework (and the DragAndDrop class) it’s a piece of cake. The last point on the list means that you are even able to use this to drag and drop items (for example armor/weapons) on a character slot to activate those, or to an action bar with hotkeys, or into a crafting grid like in minecraft.

The end result might look like this, but can easily be changed with another Skin.

Inventory preview.

Click here for a live preview

The full code can be found here in our public repository on GitHub.

This tutorial is divided into four seperate parts:

To start with the first part, click here.

3D ingame UI with scene2d.ui in LibGDX

2D user interfaces in a 3D environment are something pretty cool. Probably the most well-known game for utilizing UIs in very immersive way is Dead Space.

Dead Space three dimensional ingame UI

Another possible application of this are ingame computer screens with minigames, or menus.

Computer screens in Dead Space with interactive interfaces

How can anything like this be achieved with LibGDX? That’s super easy!

UIs in LibGDX are usually made with the help of a Stage and Actors. The scene2d.ui package offers a bunch of useful widgets like Label, Button, ScrollPane and many more. But by default those will be drawn with an OrthogonalCamera into the X/Y plane.

Now how to get those in a 3D enironment? We just replace the camera! See this forum thread for NateS original post about that.

float width = Gdx.graphics.getWidth();
float height = Gdx.graphics.getHeight();
PerspectiveCamera camera = new PerspectiveCamera(90, width, height);
camera.position.set(width / 2, height / 2 - 200, 200);
camera.near = 0.1f;
camera.far = 1000;
camera.rotate(30, 1, 0, 0);
stage.setCamera(camera);

Since the standard Stage class doesn’t expect a PerspectiveCamera, we need to adjust the way picking (conversion from screen coordinates to stage coordinates) is done.

public class Stage3D extends Stage {

    @Override
    public Vector2 screenToStageCoordinates (Vector2 screenCoords) {
        Ray pickRay = getViewport().getPickRay(screenCoords.x, screenCoords.y);
        Vector3 intersection = new Vector3(0, 0, 1);
        if (Intersector.intersectRayPlane(pickRay, new Plane(new Vector3(0, 0, 1), Vector3.Zero), intersection)) {
                screenCoords.x = intersection.x;
                screenCoords.y = intersection.y;
        } else {
                screenCoords.x = Float.MAX_VALUE;
                screenCoords.y = Float.MAX_VALUE;
        }
        return screenCoords;
    }

}

Another problem is that clipping (which is done via the ScissorStack) won’t be correct anymore, due to the new perspective. That might look like the next picture. Note that it doesn’t look like it, but it’s actually a rectangle which has been cut out because of the clipping.

Parts of the UI being cut off because of clipping

By default clipping is used for all widgets that are based on Table. Some of them need clipping, for example the ScrollPane, but a Window usually doesn’t. As a quick workaround for that problem it is possible to disabled clipping on all widgets via setClip(false).

The end result could look like the following. The camera is controlled via a CameraInputController. Notice that the input is perfectly accurate.

An inventory in a 3D environment.