Categories
Advanced Tutorials

Introduction to Widgets, building a game UI

Preface

Almost every game requires, in one form or the other, some sort of user interface (UI). For that reason most of the game engines or game creation systems go to great lengths in providing a robust UI solution to the developer.

In Crayta you can use a well established tech stack to build the UI for your game: HTML/CSS and Javascript. If you are coming from a web dev background you will feel right at home. If not, in this tutorial we will attempt to provide simple steps to get you started.

If you are coming from Unity to Crayta and you have been using the Unity User Interface (Unity UI), initially you might find it difficult to adapt to the Crayta UI.

At the moment, there isn’t a visual editor to allow you to place elements visually, adjust the position, the properties etc. All Crayta UI elements are HTML elements that are styled using CSS and logic is added using Javascript.

You can find a plethora of resources online to help you get started with HTML, CSS and Javascript.

If you’ve used the new UIElements in Unity you will feel much more comfortable.

Widgets

All interface related code is being stored in a special asset named Widget which can be attached to entities. You add widgets to your entities in the same way that you add scripts.

There are three types of widgets that can render an interface:

  1. Screen: the UI will be rendered as a HUD on screen.
  2. World: the UI will be rendered as a texture in the 3D world on a 3D plane.
  3. World – Camera Facing: the UI will be rendered as a texture in the 3D world, as a camera facing 3D plane.

The type of a widget can be set using the type property.

The Widget references an asset in its html property that contains the HTML code used for rendering it, plus inline CSS and inline Javascript.

What is HTML?

HTML is a markup language used to render web pages that has been used since the very first websites went public on the internet. Over time it has evolved to be able to handle 2D and 3D styles and also include complex logic using Javascript.

<!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>

<p>My first paragraph.</p>

</body>
</html>

What is CSS?

The tags (e.g. <p></p>) used to define the HTML markup structure have a basic styling of their own. To extend or change that styling with your own, a special language was created called CSS (Cascading Style Sheets).

This language provides you with a number of rules that extend the styling of any HTML element. Here is an example of inline (meaning in the same file) CSS:

<html>
  <head>
    <title>Playing with Inline Styles</title>
      <style>
         p{
            margin-left: 20px;
         }
      </style>
  </head>
  <body>
    <p style="color:blue;font-size:46px;">
      I'm a big, blue, <strong>strong</strong> paragraph
    </p>
  </body>
</html>

CSS can be added directly to any element using the style attribute or using a special HTML element <style>.

What is Javascript?

Javascript, a turing complete language, is used for adding logic and interactivity to web pages and even to write complex web applications.

Crayta includes the V8 Javascript engine which is the same Javascript compiler used by Chrome and other applications. This means that you are able to run and execute any kind of Javascript code in Crayta.

Crayta exposes a clean API that can be used to communicate data back and forth to your Lua scripts in your Javascript code.

    <body>
       <p style="color:blue;font-size:46px;">
           I'm a big, blue, <strong>strong</strong> paragraph
       </p>

        <script>
            engine.createJSModel('data',
            {
                bgColor: "rgb(20, 200, 100)",
                
                title: "Sign Title",
                titleSize: 20,
                titleColor: "rgb(255, 255, 255)",
                
                message: "Sign Message",
                messageSize: 10,
                messageColor: "rgb(20, 20, 20)",
            });
        </script>
    </body>

To write Javascript in HTML you can use the special HTML element <script>, in the <head> and/or <body> elements.

It’s common practice to write your Javascript code at the end of your <body> element, since this allows you to access any other HTML elements in the script.

Building a simple UI

A lot of the Crayta provided packages come with one or more widgets to draw a UI. You can use them to start learning how to build a UI of your own or as a starting point for your game UI.

But to better understand how widgets work in Crayta, we will explore how we can get started building a UI from scratch.

A simple game

Let’s take a moment and create a simple game and use it as the base for our interface.

  • You can create a new blank project or work on your existing one. 
  • Place a number of random Mesh entities around the world.
  • Create a new script called collectableScript and attach it to all the entities you’ve placed.
  • Add the following code to the script:
local CollectableScript = {}

function CollectableScript:OnCollision()
    self:GetEntity():Destroy()
end

return CollectableScript

If you go and play the game now, as soon as the Player collides with an object it will be removed from the world, like a collectible.

Now that’s not so much fun since the Player can’t see which items they’ve picked up. Our interface to the rescue!

Adding a widget

  • Select the UI Widgets category from your Library, and create a new asset named myInterfaceWidget

You can have all of your interface in a single widget, or split it up into smaller widgets that can act as parts or components of your interface. The latter approach is particularly beneficial If you have a complex interface or plan on reusing parts of it in other games in the long term.

For our tutorial project we will be using a single widget. Crayta will automatically fill the widget html file with boilerplate code that includes placeholders for CSS, HTML and basic Javascript methods required to communicate with your Lua scripts.

  • Attach this widget to your Player template.

Running the game will result in this:

Let’s prepare the widget to host our interface code.

  • Remove all existing CSS code from your <style> element.
        <style type="text/css">
            /* YOUR CSS CODE HERE */
        </style>
  • Remove all html code from your <body> element, and Javascript from the script element.
    <body>
        <!-- YOUR HTML CODE HERE -->

        <script>
            /* YOUR JAVASCRIPT CODE HERE */
        </script>
    </body>

Let’s add a simple list that will serve as a container for all the collectables the Player gathers.

  • Add the following HTML code where you’ve removed the boilerplate one (at the top of the body section and under the <!– YOUR HTML CODE HERE –> comment).
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div class="my-item">
                    <span>1</span>
                    <p>Item Collected</p>
                </div>
            </div>
        </div>
  • Add some CSS to make it look pretty inside the style element, under the /* YOUR CSS CODE HERE */ comment:
    .my-items-container {
        width: 30vh;
        margin: 10px;
        padding: 10px;
        border-radius: 7px;
        background-color: rgba(255,255,255,0.5);
    }
    
    .my-items-container h1{
        text-align: center;
        text-shadow: 0px 0px 5px white;
    }

    .my-items-list {
        color: #ccc;
        list-style-type: none;
    }
    
    .my-item{
        position: relative;
        font: bold italic 70px/1.5 sans-serif;
        margin-bottom: 20px;
    }
    
    .my-item p {
        font: 20px/1.5 sans-serif;
        padding-left: 60px;
        color: #555;
    }
    
    .my-item span {
        position: absolute;
        color: darkcyan;
    }

Running the game at this point:

Although it is a nice list, right now it is just a graphic. Let’s add some logic to the interface.

Adding logic

Our goal for this list is to list any collectable the player picks up. For that we need a Lua script on the Player template that keeps tracks of the items collected.

  • Add a script to the Player template called myCollectablesScript.
  • And add the following code to it:
local MyCollectablesScript = {}

function MyCollectablesScript:Collected(collectableName)
    
    self:GetEntity().myInterfaceWidget.js:CallFunction("collected", collectableName)
end

return MyCollectablesScript

This code is the starting point of establishing communication between our Lua scripts and the Widget.

MyCollectablesScript:Collected is a custom method that we will be calling each time the Player has picked up a collectable. Let’s implement that now.

  • Add the following code to the collectableScript.
local CollectableScript = {}

function CollectableScript:Init()
    self.collected = false
end

function CollectableScript:OnCollision(collidingPlayerOrEntity)
    
    if self.collected == true then return end
    self.collected = true
    
    -- destroy the collectable
    self:GetEntity():Destroy()
    
    -- inform the Player/UI of the item collected
    if collidingPlayerOrEntity:IsA(Character) then
        collidingPlayerOrEntity:SendToLocal('Collected', self:GetEntity():GetName())    
    end
    
end

return CollectableScript

Using SendToLocal on the Player entity is an easy way to communicate from the server to the local Player the item that this Player has collected.

  • Add the following Javascript code to the widget, replacing the current code.
      <script>
        /* YOUR JAVASCRIPT CODE HERE */

        engine.createJSModel('itemsModel',
        {
            items: []
        });

        engine.on("collected", function(collectableName) {
            itemsModel.items.push({
                index: itemsModel.items.length + 1,
                name: collectableName
            });
            engine.updateWholeModel(itemsModel);
            engine.synchronizeModels();
        });

    </script>

This Javascript code does two things:

  1. Creates a new model that contains an array property used to hold all of the collected items.
  2. Registers an event handler, that is a function that can be called from Lua code and can also accept arguments.

As soon as the Player picks up a collectable the widget will get notified, and the items array will get populated.

Dynamically updating the UI

Let’s explore how the interface can automatically get updated as the items are collected.

Crayta uses the Coherent Gameface UI library which allows you to add data bindings to any HTML element. This is powerful for the elements which can get updated/added/removed automatically reflecting your data.

  • Update the HTML code in the widget adding data attributes.
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div data-bind-for="item:{{itemsModel.items}}"              class="my-item">
                    <span data-bind-value="{{item}}.index"></span>
                    <p data-bind-value="{{item}}.name"></p>
                </div>
            </div>
        </div>

There are a number of data-bind attributes available to use in your HTML code. Some common ones are:

  • data-bind-for: can be attached to an array and automatically clone this HTML element, together with its children, for each array item.
  • data-bind-if: can do conditionals to check and show/hide an HTML element based on that condition.
  • data-bind-value: automatically update the innerHTML value of any element with the value attached.

Now this HTML code will automatically reflect any changes to the itemsModel object.

Every item collected by the Player will now appear on the Interface.

Code

Here is the complete widget script:

<html>
    <head>
        <!-- Required includes -->
        <script type="text/javascript" src="coui://uiresources/js/crayta.js"></script>

        <style type="text/css">
            /* YOUR CSS CODE HERE */
            .my-items-container {
              width: 30vh;
              margin: 10px;
              padding: 10px;
              border-radius: 7px;
              background-color: rgba(255,255,255,0.5);
            }
            
            .my-items-container h1{
                text-align: center;
                text-shadow: 0px 0px 5px white;
            }

            .my-items-list {
              color: #ccc;
              list-style-type: none;
            }
            
            .my-item{
              position: relative;
              font: bold italic 70px/1.5 sans-serif;
              margin-bottom: 20px;
            }
            
            .my-item p {
              font: 20px/1.5 sans-serif;
              padding-left: 60px;
              color: #555;
            }
            
            .my-item span {
              position: absolute;
              color: darkcyan;
            }
        </style>
    </head>

    <body>
        <!-- YOUR HTML CODE HERE -->
        <div class="my-items-container">
            <h1>My items</h1>
            <div class="my-items-list">
                <div data-bind-for="item:{{itemsModel.items}}" class="my-item">
                    <span data-bind-value="{{item}}.index"></span>
                    <p data-bind-value="{{item}}.name"></p>
                </div>
            </div>
        </div>

        <script>
            /* YOUR JAVASCRIPT CODE HERE */

            engine.createJSModel('itemsModel',
            {
                items: []
            });

            engine.on("collected", function(collectableName) {
                itemsModel.items.push({
                    index: itemsModel.items.length + 1,
                    name: collectableName
                });
                engine.updateWholeModel(itemsModel);
                engine.synchronizeModels();
            });

        </script>
    </body>
</html>

Useful links

Coherent Gameface reference docs: