Skip to content

UI, Font and HUD

Brov3r edited this page Aug 11, 2024 · 3 revisions

Introduction

Starting from version 1.4.0, it is now possible to create a user graphical interface using NanoVG. This library allows you to create beautiful user interfaces with good performance.

Important

Everything described here works exclusively in client plugins

Font / Image

As an example, we will upload a custom font Endeavourforever and two images (local and over the Internet). Let's put the font and one of the images in the Jar resources of the plugin in the media folder. After that, we will create an event handler OnWidgetManagerInitEvent and load our resources.

IMPORTANT! All manipulations with NanoVG must occur exclusively after the full initialization of the WidgetManager (the OnWidgetManagerInitEvent), otherwise an exception will be raised.

By default, Avrix contains several default fonts: Arial-Regular, Roboto-Regular, Montserrat-Regular, FontAwesome

/**
 * Handle widget manager init
 */
public class WidgetManagerInitHandler extends OnWidgetManagerInitEvent {
    public static File jarCoreFile;

    /**
     * Called Event Handling Method
     *
     * @param context {@link NanoContext} in which NanoVG is initialized
     */
    @Override
    public void handleEvent(NanoContext context) {
        // Load custom font and image
        try {
            jarCoreFile = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI());
            NanoFont.createFont("Endeavourforever", jarCoreFile.getPath(), "media/Endeavourforever.ttf");
        } catch (Exception e) {
            System.out.println("[!] Failed to load resources: " + e.getMessage());
        }
    }
}

After successful download, you can work with these resources, i.e. the font can be used simply by specifying its names, and images by ID. After downloading the images, they will be cached in the root folder of the game avrix\cache

HUD

Downloaded early fonts and images can be used, for example, to render HUD. There are two events OnPreWidgetDrawEvent and OnPostWidgetDrawEvent, which are called before and after rendering widgets, respectively. As an example, let's create a handler for the first event (before rendering widgets, HUD will always be behind):

/**
 * Draw HUD
 */
public class HUDHandler extends OnPreWidgetDrawEvent {
    private static final float AMPLITUDE = 10.0f; // Maximum up and down displacement
    private static final float FREQUENCY = 5.0f; // Motion frequency (cycles per second)
    private float phase = 0.0f; // Sine wave phase to calculate vertical position
    private long lastTime = System.currentTimeMillis();

    private float smoothedFPS = GameWindow.averageFPS;
    private static final float SMOOTHING_FACTOR = 0.01f;


    /**
     * Called Event Handling Method
     *
     * @param context {@link NanoContext} in which NanoVG is initialized
     */
    @Override
    public void handleEvent(NanoContext context) {
        smoothedFPS += SMOOTHING_FACTOR * (GameWindow.averageFPS - smoothedFPS);
        String fps = String.format("FPS: %.0f", smoothedFPS);

        NanoDrawer.drawText("Hello client plugin!", "Endeavourforever", 10, 10, 32, NanoColor.ORANGE); // Custom font
        NanoDrawer.drawText(fps, "Montserrat-Regular", 10, WindowUtils.getWindowHeight() - 24, 14, NanoColor.ORANGE); // Default font

        // Animation :)
        long currentTime = System.currentTimeMillis();
        float deltaTime = (currentTime - lastTime) / 1000.0f;
        lastTime = currentTime;

        phase += FREQUENCY * deltaTime;
        if (phase > 2 * Math.PI) {
            phase -= (float) (2 * Math.PI);
        }

        float y = 70 + AMPLITUDE * (float) Math.sin(phase);
        float x = 120 + AMPLITUDE * (float) Math.cos(phase);


        NanoDrawer.drawImage(NanoImage.loadImage(WidgetManagerInitHandler.jarCoreFile.getAbsolutePath(), "media/image_test.jpg"), 10, (int) y, 100, 100, 1); // Rendering an earlier uploaded image
        NanoDrawer.drawImage(NanoImage.loadImage("https://gas-kvas.com/uploads/posts/2023-02/1675462147_gas-kvas-com-p-fonovii-risunok-2k-2.jpg"), (int) x, (int) y, 100, 100, 1); // Rendering an earlier uploaded image
    }
}

Widgets

The entire user interface is designed according to the principle of widgets, where everyone can become a child of one. Building the UI is very similar to the in-game UI system Project Zomboid. In general, creating widgets is allowed at any point in the plugin, but as an example, we will create them in the Main class. Avril contains several default widgets from which you can inherit and create your own.

/**
 * Main entry point of the example plugin
 */
public class Main extends Plugin {
    /**
     * Constructs a new {@link Plugin} with the specified metadata.
     * Metadata is transferred when the plugin is loaded into the game context.
     *
     * @param metadata The {@link Metadata} associated with this plugin.
     */
    public Main(Metadata metadata) {
        super(metadata);
    }

    /**
     * Called when the plugin is initialized.
     * <p>
     * Implementing classes should override this method to provide the initialization logic.
     */
    @Override
    public void onInitialize() {
        EventManager.addListener(new HUDHandler());
        EventManager.addListener(new WidgetManagerInitHandler());

        WindowWidget root = new WindowWidget("Hello window", 10, 150, 300, 400);
        root.setResizable(true);
        root.setBorderRadius(8);
        root.setDraggable(true);

        ButtonWidget btn = new ButtonWidget("Example button", 10, 50, 100, 32, 0, NanoColor.BABY_BLUE, () -> { 
        // On click logic
        });
        root.addChild(btn);

        root.addToScreen(); // Adding a widget to the main game screen
    }
}
Clone this wiki locally