The Overlay UI is exactly what it sounds like. A UI that overlays your game. You can use it to create any type of UI you'd like.
Overlay UI is loaded into an absolutely positioned <div> on top of the game scene that spans the full width and height of the window.
All Overlay UI is defined in the .html file used to load your UI when invoking player.ui.load()for a player from the server.
Creating A RPG Skills UI Example
We'll use the Overlay UI to create a skills based UI that looks like something that would belong in an RPG (role-playing game) as an overlay in our game.
First, let's make sure we've created our index.htmlfile at assets/ui/index.html.
In our index.html, we'll add the following HTML & CSS to create our UI. In the same exact way you build standard web pages, you can build your HYTOPIA Overlay UI.
Now, on our server, when a player joins our game we'll load the UI file we created for them.
We can do that as follows.
world.onPlayerJoin= player => {player.ui.load('ui/index.html');// ... other code};
That's it! That's how easy it is to create a basic Overlay UI in HYTOPIA!
If all went well, we should see an awesome skills menu that looks like this.
Sending Data From Server To UI
In our previous example, we created our skills menu UI, but it doesn't update or change based on gameplay.
Let's expand on it so that we can communicate things like updates to the player's skill levels from the server.
To do this, we need our UI to listen for data from the server. Here's how we can update our index.htmlfile to listen for this data. Let's add this script to the top of the file
<!-- Top of our index.html file --><script>hytopia.onData(data => { // data is any arbitrary object you send from the serverif (data.type ==='mining-level') {document.querySelector('.mining-level').textContent =data.level; }if (data.type ==='woodcutting-level') {document.querySelector('.woodcutting-level').textContent =data.level; }if (data.type ==='fishing-level') {document.querySelector('.fishing-level').textContent =data.level; }if (data.type ==='combat-level') {document.querySelector('.combat-level').textContent =data.level; } });</script><!-- ... The rest of our index.html from the previous example ... -->
Perfect, our UI is ready to listen for data from our server.
For the sake of showcasing how data works, let's do something simple like set the level of our player's different skills to a random value every second, controlled by the server.
Here's how we can do that.
world.onPlayerJoin= player => {player.ui.load('ui/demo.html');// Notice that .sendData is specific to the player. We can// control sending data uniquely to each individual player as// needed through their player.uisetInterval(() => {player.ui.sendData({ type:'mining-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'woodcutting-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'fishing-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'combat-level', level:Math.floor(Math.random() *100) }); },1000);// ... other code};
That's it! That's all we have to do to send data to the UI of a specific player.
Here's what we should see in our Overlay UI.
Sending Data From UI To Server
Let's end on one final example. We're sending data down to our UI, but what if we need to send data from our UI back to our server?
We can do that as well, and receive that data on the server with a reference to the player it came from, allowing us to fully scope any UI and game behavior specific to each player if necessary.
In our index.htmlfile for our UI that we created in the previous example, we'll add the following to our script.
<!-- Top of our index.html file --><script>// Send "ping" data from our UI to server// sendData() can send any arbitrary object// with any JSON compatible data.setInterval(() => {hytopia.sendData({ hello:'world!' }); },2000);//////hytopia.onData(data => { // data is any arbitrary object you send from the serverif (data.type ==='mining-level') {document.querySelector('.mining-level').textContent =data.level; }if (data.type ==='woodcutting-level') {document.querySelector('.woodcutting-level').textContent =data.level; }if (data.type ==='fishing-level') {document.querySelector('.fishing-level').textContent =data.level; }if (data.type ==='combat-level') {document.querySelector('.combat-level').textContent =data.level; } });</script><!-- ... The rest of our index.html from the previous example ... -->
Now, on our server we can listen for data from our player like this.
world.onPlayerJoin= player => {player.ui.load('ui/demo.html');setInterval(() => {player.ui.sendData({ type:'mining-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'woodcutting-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'fishing-level', level:Math.floor(Math.random() *100) });player.ui.sendData({ type:'combat-level', level:Math.floor(Math.random() *100) }); },1000);player.ui.onData= (playerUI:PlayerUI, data:object) => {console.log('got data from this players UI!', data);// We can also get the player the data came from by// playerUI.player if ever needed. };// ... other code};
Now, in our server console, we should see a console.log every second for the data sent up from the UI.
That's it! You can expand on these concepts of sending and receiving data however you'd like. The interface for data communication was left intentionally simple and uses generic JSON compatible objects to allow you to create whatever data structures and interactions you need for your specific game.
Explicitly Controlling Pointer Lock
If you need to programmatically unlock a player's cursor lock, which hides their cursor while they're controlling their character in game, we can do that directly from our server code.
By default, a user must press Escapeor Tto unlock their cursor to interact with UI elements. This isn't a great user experience if a menu suddenly pops up for your game, or they interact with something in game that results in a UI change that also requires interaction. They'd have to manually unlock their own pointer with Escape or T, and that's annoying.
On our server, we can lock and unlock a player's cursor at any time with the following code.
// To unlock their pointerplayer.ui.lockPointer(false);// To lock their pointerplayer.ui.lockPointer(true);
Simple! Now, we can better control the UI experience of a player based on everything from in game interactions, UI interactions, and more.
Diving Deeper
The Overlay UI related systems are constantly evolving. You can find the latest PlayerUI API Reference here.
If there are features that we don't currently support for Overlay UI that you'd like to see added to the HYTOPIA SDK, you can submit a feature request here.