| source |
|---|
NoesisUI introduces Shared mode—a new scripting execution mode that simplifies per-player UI interactions by eliminating the need for entity cloning and ownership transfer. Understanding the differences between scripting modes is essential for building performant and responsive UI experiences in your world.
CustomUI 1.0 supports two scripting modes: Default and Local.
In Default mode, a single script instance runs on a Master client and controls CustomUI for all users. Networking operations required to propagate binding values are performed automatically under the hood. While this makes it easier to use, interactions in this mode are laggy (roundtrip time between a Master client and a player client is ~300ms).
In Local mode, the script controlling CustomUI follows entity ownership and can be run locally on a player client. This makes interaction faster but because only one player can own any given CustomUI entity at a time, this mode requires entity pooling (creating a separate clone of a CustomUI entity for each player) and ownership transfer, usually happening when a new player enters the world. Making it fast and reliable is known to be tricky.
NoesisUI is designed to avoid the hidden networking complexity and lagginess of bindings (also known as DataContexts). As a result, Noesis only supports local UIs, and the following limitations apply:
- Default mode scripts won’t work: An attempt to set Noesis dataContext on a Master client will only result in warnings in the logs with no other visible effects.
- Local mode scripts work with limitations: Local mode scripts function for Noesis similarly to CustomUI, but with the same set of drawbacks caused by entity cloning and ownership transfer.
The recently introduced Shared mode removes the need for NoesisUI entity cloning and ownership transfer by running an attached script on all clients (including the Master client) at the same time.
- Makes it easy to set DataContexts individually for every player.
- Eliminates the complexity of entity pooling and ownership transfer.
- Provides a simpler approach to managing per-player UI interactions.
An important drawback of the Shared mode approach is that as it’s a single shared NoesisUI entity, its generic properties such as position and size are shared by all players as well, and can’t be modified per-player. This is generally not a problem for stationary panels and HUDs but may become an issue in more dynamic cases.
Here’s an example created by flatpixel showing how a shared script can communicate between the server and clients through events, allowing each player to update their own UI data dynamically.
Important Distinction: Don’t confuse this.world.owner.get() with this.world.getLocalPlayer(). The local player refers to the current client’s player, while the owner refers to the entity’s owner.
import
{
CodeBlockEvents
,
Component
,
NetworkEvent
,
Player
}
from
'horizon/core'
;
import
{
NoesisGizmo
}
from
'horizon/noesis'
;
const
testUIEvent
=
new
NetworkEvent
<{
greeting
:
string
}>(
"testUIEvent"
);
class
testUI
extends
Component
<
typeof
testUI
>
{
start
()
{
if
(
this
.
world
.
getLocalPlayer
().
id
===
this
.
world
.
getServerPlayer
().
id
)
{
this
.
startServer
();
}
else
{
this
.
startClient
();
}
}
private
startServer
()
{
this
.
connectCodeBlockEvent
(
this
.
entity
,
CodeBlockEvents
.
OnPlayerEnterWorld
,
(
player
:
Player
)
=>
{
console
.
log
(
'NoesisUI: OnPlayerEnterWorld'
,
player
.
name
.
get
());
this
.
sendNetworkEvent
(
player
,
testUIEvent
,
{
greeting
:
`Welcome ${player.name.get()}`
});
});
}
private
startClient
()
{
const
dataContext
=
{
label
:
"NoesisGUI"
,
nested
:
{
text
:
"Hello World"
,
},
items
:
[
{
label
:
"Item 1"
,
},
{
label
:
"Item 2"
,
},
],
command
:
()
=>
{
console
.
log
(
"Command invoked"
);
dataContext
.
nested
.
text
=
"Boom!"
;
}
};
this
.
entity
.
as
(
NoesisGizmo
).
dataContext
=
dataContext
;
this
.
connectNetworkEvent
(
this
.
world
.
getLocalPlayer
(),
testUIEvent
,
data
=>
{
console
.
log
(
'NoesisUI: OnEvent'
,
data
);
dataContext
.
label
=
data
.
greeting
;
});
}
}
Component
.
register
(
testUI
);