User Interface

Table of Contents

You can use scripting to create user interface elements like buttons or text fields in the world. When players interact with the UI elements, they generate events that you can subscribe to. The following examples will assume that you already know your way around Tabletop Playground scripting, see Scripting basics to get started.

Object UI

There are two ways to add UI elements: globally or attached to an object. In both cases, you create a widget (like a button or check box), and you create a UIElement to determine how and where your widget is shown in the game. For example, here’s how to add a large text above an object:

var ui = new UIElement();
ui.position = new Vector(0, 0, 5); 
ui.widget = new Text().setText("Look at me!").setFontSize(28);
refObject.attachUI(ui);

Note how the the set methods on the widget return the widget itself so you can use method chaining. When the object is moved, the text will be moved along with it.

Global UI

When you want to add UI at a fixed position instead of attached to an object, you can use the the methods from GameWorld. For example, button at the center of the table that prints a message when pressed can be created like this:

var printButton = new Button().setText("Print");
printButton.onClicked.add((button, player) => {
    console.log(player.getName(), "pressed the button!"); 
});

var ui = new UIElement();
ui.position = new Vector(0, 0, 80.5); // For 80 cm table height 
ui.widget = printButton;
world.addUI(ui);

Hit point counter example

That’s already all you need to know for creating a custom user interface for your game! Let’s try a more complex example: a hit point counter. It will have three components: a progress bar to show the current hit points and two buttons to increase or decrease the hit point count. We will implement it as a reusable function. You could have this function in a separate file that you import using require, or even in an npm library! For this tutorial, we’ll just leave it in the object script file.

const { Vector, Rotator, ProgressBar, Button, UIElement, refObject } = require('@tabletop-playground/api');

function addHitPointCounter(hitPointObj, maxHitPoints) {
    function setBarValue(obj) {
        obj.progressBar
            .setText((`${obj.hitPoints}/${obj.maxHitPoints}`))
            .setProgress(obj.hitPoints / obj.maxHitPoints);
    }
    
    function minusPressed(button, player) {
        var owner = button.getOwningObject();
        if (owner.hitPoints <= 1) {
            // Final hit point lost, die!
            owner.destroy();
        }
        else {
            owner.hitPoints--;
            setBarValue(owner);
        }
    }
    
    function plusPressed(button, player) {
        var owner = button.getOwningObject();
        owner.hitPoints = Math.min(owner.hitPoints + 1, owner.maxHitPoints);
        setBarValue(owner);
    }
    
    // Add hit point variables to the object
    hitPointObj.hitPoints = maxHitPoints;
    hitPointObj.maxHitPoints = maxHitPoints;
    
    // Create widgets
    hitPointObj.progressBar = new ProgressBar();
    setBarValue(hitPointObj);
    
    var minusButton = new Button().setText("-");
    minusButton.onClicked.add(minusPressed);
    
    var plusButton = new Button().setText("+");
    plusButton.onClicked.add(plusPressed);
    
    // Add the three UI elements next to each other at an angle
    var ui = new UIElement();
    ui.useWidgetSize = false;
    ui.width = 60;
    ui.height = 30;
    ui.position = new Vector(-3, 0, 0);
    ui.rotation = new Rotator(25, 0, 0);
    ui.scale = 0.5;
    ui.widget = refObject.progressBar;
    hitPointObj.attachUI(ui);
    
    var ui2 = new UIElement();
    ui2.useWidgetSize = false;
    ui2.width = 25;
    ui2.height = 35;
    ui2.position = new Vector(-3, -1.85, 0);
    ui2.rotation = new Rotator(25, 0, 0);
    ui2.scale = 0.4;
    ui2.widget = minusButton;
    hitPointObj.attachUI(ui2);
    
    var ui3 = new UIElement();
    ui2.useWidgetSize = false;
    ui2.width = 25;
    ui2.height = 35;
    ui3.position = new Vector(-3, 1.85, 0);
    ui3.rotation = new Rotator(25, 0, 0);
    ui3.scale = 0.4;
    ui3.widget = plusButton;
    hitPointObj.attachUI(ui3);
}

// Add a counter for 10 hit points to our reference object
addHitPointCounter(refObject, 10);

Note that we used more properties of UIElement in this example: By default, the UI element will just as large as necessary, and one UI pixel corresponds to one millimeter in the 3D world. When you set useWidgetSize to false, the pixel size is determined by the width and height properties. With scale, you can determine how big each pixel should be in the 3D world.

Available widgets

You can find all the available widgets in the API documentation at the list of subclasses for Widget. There’s one special widget: Border is used as a container for other widgets when you want a background. For example, a Slider usually has a transparent background, and you can wrap it in a border to make it easier to see:

var slider = new Slider().setMaxValue(10);
var ui = new UIElement();
ui.widget = new Border().setChild(slider);
refObject.attachUI(ui);
Slider without and with border