ServerUI: Creating client-side UI directly from the server

Started by PSL, Today at 10:42 AM

Previous topic - Next topic

PSL

I'd like to share a highly practical component that allows you to create client-side UI directly from your server-side code—eliminating the need to manually write extensive UI logic in client-side scripts.

This component relies on the # Remote-Exec plugin. The core concept is to synchronize UI instances and their properties to the client via remote execution; the server simply constructs the UI and defines its styles and parameters.

You can generate various GUI elements—such as text, labels, and layouts—directly on the server, with full support for dynamic property updates. This means that elements like welcome messages, status bars, countdown timers, HUDs, or vehicle information can have their logic handled entirely on the server, while the client automatically reflects these changes.

This solution is ideal for building server management interfaces, player status displays, or interactive prompts—perfect for any scenario where you need the server to control the client-side interface.

Please note that this component is not yet fully functional, but the underlying framework is complete—and I simply couldn't wait to share it!

Note that this component depends on Habi's RemoteExec plugin: https://forum.vc-mp.org/index.php?topic=9276.0

function onPlayerJoin(player) {
ServerUI.Init(player);
ServerUI.Build(player, "myLabel", function() {
local screenSize = GUI.GetScreenSize();
local me = World.FindLocalPlayer();
return GUILabel(
VectorScreen(screenSize.X * 0.005, screenSize.Y * 0.96),
Colour(255, 255, 255, 255),
format("Hey, %s. Welcome to the server!", me.Name)
);
}, function(ui) {
ui.FontName = "Verdana";
ui.FontSize = 36;
ui.FontFlags = GUI_FFLAG_BOLD;
ui.TextAlignment = GUI_ALIGN_RIGHT;
ui.TextColour = Colour(50, 205, 50, 255);
});
}

The code above can generate a welcome message in the bottom-right corner of the screen. 
The Init function is used for initialization; it generates a unique table on the client side.
The Build function is used to create a GUI; it takes the player's unique key that identifies the GUI, a function for creating the GUI, and a function for modifying the GUI after its creation.

As demonstrated in the code, you can directly use functions, classes, constants, and so on, from the client side.

If you like this type of component, you are welcome to join in expanding it into a more comprehensive server-side UI framework.

ServerUI.nut  If the link doesn't work, copy the code below.

ServerUI <- {
table = GetRemoteValue("getroottable")()
consttable = GetRemoteValue("getconsttable")()

function Init(player) {
RemoteExec(SetRemoteValue("clientUIMap", {}), player);
}

GUI = {
table = GetRemoteValue("getroottable")()
function GetMouseEnabled() {
return table.GUI.GetMouseEnabled();
}
function SetMouseEnabled(bool) {
return table.GUI.SetMouseEnabled(bool);
}
function GetMousePos() {
return table.GUI.GetMousePos();
}
function GetScreenSize() {
return table.GUI.GetScreenSize();
}
function ScreenPosToWorld(vectorScreen) {
return table.GUI.ScreenPosToWorld(vectorScreen);
}
function WorldPosToScreen(pos) {
return table.GUI.WorldPosToScreen(pos);
}
function GetFocusedElement() {
return table.GUI.GetFocusedElement();
}
function SetFocusedElement(ui) {
return table.GUI.SetFocusedElement(ui);
}
}

World = {
table = GetRemoteValue("getroottable")()
function FindLocalPlayer() {
return table.World.FindLocalPlayer();
}
}

function VectorScreen(x, y) {
return table.VectorScreen(x, y);
}

function Colour(r, g, b, a) {
return table.Colour(r, g, b, a);
}

function GUILabel(pos, color, string) {
return table.GUILabel(pos, color, string);
}

function format(string, ...) {
local c_format = GetRemoteValueEx(table, "format");
vargv.insert(0, this);
vargv.insert(1, c_format);
vargv.insert(2, table);
vargv.insert(3, string);
return CallRemoteFuncEx.acall(vargv);
}

function Build(player, key, func, setFunc) {
func.bindenv(this);
local ui = func();

local clientUIMap = GetRemoteValue("clientUIMap");
RemoteExec(SetRemoteValueEx(clientUIMap, key, ui), player);

local uiProp = {};
uiProp.setdelegate({
data = {}
_set = function(key, value) {
data[key] <- value;
}
_get = function(key) {
if (key in data) {
return data[key];
}
return null;
}
});

setFunc.acall([this, uiProp]);

local myUI = GetRemoteValueEx(clientUIMap, key);
foreach (key, value in uiProp.data) {
RemoteExec(SetRemoteValueEx(myUI, key, value), player);
}
}
}

ServerUI.GUI_FFLAG_BOLD <- ServerUI.consttable.GUI_FFLAG_BOLD
ServerUI.GUI_ALIGN_RIGHT <- ServerUI.consttable.GUI_ALIGN_RIGHT