UI Getting Started

This article is about Camelot Unchained UI development. It is not intended as a tutorial for html5 or JavaScript, and assumes basic knowledge of both.

Note that this article is subject to change significantly throughout the course of the development of Camelot Unchained.

Background
Each user interface element runs in an isolated instance of the browser and gets it's own client.exe process. Communication with the game client is handled through the cuAPI interface provided by the game client.

The Basics
Each user interface is defined by it's .ui configuration file, for example chat.ui is the file which describes the chat UI.

These files are found in the game installation folder bin/ /client/assets/webui where is a number indicating a particular instance of the client. During the internal testing phase, these instances are numbered 4 (internal test client) and 10 (pre-alpha client). I will be using instance 4 (internal test client) in the examples in this document.

The chat.ui configuration file looks like this

Hello World - your first UI
Now we know how to kick start a UI, lets begin with the obligatory Hello World example. Remember that we are working in the  folder for this. I recommend using notepad++ for simple html/javascript editing, but you can use any editor or visual studio or the new Visual Studio Code

Note however, when creating the .ui file it must be created in either ANSI or UTF8 without BOM format. Visual Studio creates UTF8 files with BOM and if visual studio is used to create the .ui file it will cause an exception in the game client [ UIWindow.cpp line 290 !doc.HasParseError ] when it tries to load the UI. If you get this error, open the .ui file in notepad++ and set the encoding to ANSI or UTF8 without BOM and re-save.

Create a helloworld.ui file and save it in the webui folder.

{   "_LICENSE": "This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.", "name": "helloworld", "mainFile": "helloworld/helloworld.html", "rect": { "left": 621, "top": 280, "right": 820, "bottom": 320 } }

Create a  subfolder.

In the  folder, create a text file named   with the following HTML:

<!DOCTYPE html>  helloworld Hello World

Now to load this UI in the client, start the client then in the chat box use the  command to load your helloworld custom UI

/openui helloworld

and if everything is as it should be, you will see the words hello world, quite small, and feint in black above your character.

Lets add a stylesheet and change the look a bit.

Edit  and add the following line after the   line.



Create a  file in the   folder with the following content:

body { margin: 0; background-color: rgba(0,0,0,0.1); } #msg { text-align: center; color: white; font-size: 16pt; }

To get the client to pick up these changes, close and re-open the UI by typing the following into chat:

/closeui helloworld /openui helloworld

That's about as far as we can take the hello world UI without turning it into a not a hello world UI. So lets move on to something more useful.

Basic Combat Text UI
and it really is very basic! So like before lets create the basic UI components:

(goes in the webui folder)

{   "_LICENSE": "This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.", "name": "bct", "mainFile": "bct/bct.html", "rect": { "left": 621, "top": 280, "right": 820, "bottom": 320 } }

(goes in the bct subfolder that you will need to create)

<!DOCTYPE html>  bct 

bct.css (goes in the bct subfolder)

body { margin: 0; }    text-align: center; color: white; font-size: 16pt; font-weight: bold; }
 * 1) msg {

In order to be able to report damage to ourselves, or damage we do to others we must be able to track our own, and our targets health. To do this we need to use the following APIs

cuAPI.OnEnemyTargetHealthChanged cuAPI.OnCharacterHealthChanged

In order to be able to do this, we must tell the client we want access to these APIs. This is done through the  file by adding , and   options as follows:

{     "_LICENSE": "This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.", "name": "bct", "mainFile": "bct/bct.html", "rect": { "left": 621, "top": 280, "right": 820, "bottom": 320 },    "handlesEnemyTarget": true, "handlesCharacter": true }

Now we need to write some code to monitor the health of both self and your target, and to display combat text when the health changes. In order to do this we will add some javascript.

''Note, if developing the UI as a project, using TypeScript rather than JavaScript is recommended, but not a requirement. For this small tutorial though we will just use JavaScript.''

Add the following line to  just above

<!DOCTYPE html>  bct  &lt;div id="msg"> &lt;script src="bct.js">

And create a basic  file in the   folder (which can be used as a template for other UI code)

(function{    var init = function {       // initialise       // ... ui code goes here ...     };     if (typeof cuAPI !== "undefined") {        if (cuAPI.initialized) {  // cuAPI already initialised         init;       } else {         cuAPI.OnInitialized(init);       }     }    });

Now lets add the code to track our own and our targets health and to display the combat text when we detect damage.

Add the following code to

body { margin: 0; }  #msg { text-align: center; color: white; font-size: 16pt; font-weight: bold; } .fadeOut { opacity: 0; }  .fadeIn { opacity: 1; }

Replace the code in  with the code below (this is the full code to implement basic combat text)

(function{   var ourHealth, targetHealth, targetName, timer;      var msg = document.getElementById("msg");     var fadeOut = function {       msg.className = "fadeOut";     };     var clearTextIn = function(ms) {        if (timer) clearTimeout(timer);        timer = setTimeout(function { fadeOut; timer = null; }, ms);      };     var showCombatText = function(old, current, self) {        if (old !== undefined) {         if (old > current) {            msg.innerText = current-old;            msg.style.color = self ? 'white' : 'red';           msg.className = "fadeIn";         } else if (self) {           msg.innerText = "+" + (current-old);            msg.style.color = 'rgb(0,255,0)';           msg.className = "fadeIn";         }         clearTextIn(1000);       }     };     var init = function {        // initialise        // track our own health        cuAPI.OnCharacterHealthChanged(function(health, maxHealth) { showCombatText(ourHealth, health, true); ourHealth = health; });      // track targets health       cuAPI.OnEnemyTargetHealthChanged(function(health, maxHealth) { if (health === -1 && maxHealth === -1) { // no target } else { showCombatText(targetHealth, health, false); targetHealth = health; }      });       // track target change       cuAPI.OnEnemyTargetNameChanged(function(name) { if (name.length === 0) { targetName = targetHealth = undefined; } else { if (targetName !== name) { targetHealth = undefined; }          targetName = name; }      });     };     if (typeof cuAPI !== "undefined") {        if (cuAPI.initialized) {  // already initialised         init;       } else {         cuAPI.OnInitialized(init);       }     }    });

Don't forget to  to start the combat text UI!

A brief explanation of the code
In  we register for   and   events. In these events we call  in order to display the change in health as either damage or health gain (if this is for our own character) and then we record the new health value.

Also in  we register for the   event and use that to clear any current target health, so that we know to not display any damage info when we receive the next target health event (which will contain the initial health of the target rather than a change in health).

In  we calculate the health change, and display that value in an appropriate colour, then start a timer that will fade the text out after a defined period (1 seconds, or 1000 milliseconds) in this case.

Conclusion
The Basic Combat Text UI is meant to be used as an example of how to create a UI from scratch using the basics of the UI interface, rather than a fully functional UI, which is beyond the scope of this article.

There are a number of improvements that can be made to the UI; the main one is to have 3 separate slightly offset areas for self damage, self healing and target damage figures with their own fadeout timers. With a bit more effort these could be made to animate (float) upwards or downwards allowing the display of multiple damage indicators at once of each type.

Also the length of time the text remains visible should be relative to the damage amount, because at the moment a dot that inflicts a small amount of damage every 1 seconds, currently effectively permanently displays a -dmg value because the new damage is done just as the old value is being cleared.

Also, the whole design of this UI is flawed, because it is using change in health values to work out damage, and as such it cannot distinguish between self inflicted damage and external inflicted damage or perhaps even damage from multiple attackers. But hey, this is still only alpha!

For an improved version, which supports healing, self damage and target damage visible at the same time, see Mehuge BCT

Debugging your UIs
The client supports chrome developer tools for debugging UIs. In order to connect to the debugger for a UI, first point your brower at http://localhost:8088/ then look for the name of your UI in the list, and click the links.

The first thing to look for are error messages in the console view (at the bottom) as these will list any syntax errors or undefined functions that are preventing your UI from doing anything. The next step is to use the Source tab and set breakpoints so you can step through and inspect your code as it runs.

You can also use  to log information to the clients console log (  in the   folder) which if you can tail it using wintail or cygwin's tail command, you can watch what your UI is doing (based on the messages you log) while it is running.