-
Notifications
You must be signed in to change notification settings - Fork 2
UI, Font and HUD
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
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
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
}
}
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
}
}