UI Widgets

This week you are going to build off of what you did last week with the scene graph. As you saw last week, the FigmaTree is pretty good. But there are still some things it cannot do. Some UI elements – like ninepatches, sliders, and progress bars – have to be done by hand. So this week you are going to have to get used to editting JSON files.

While working on the previous activity, you may have realized that the scene graph JSON format has two problems. First, it can get very large, especially as you start to design more complex UIs. And second, it is very redundant as you often repeat UI elements with minor differences to them. This is going to be a problem when you edit JSONs by hand, as it is easy to get lost in them and make a mistake.

This is the purpose of widgets. A widget is a separate JSON file that used to store a reusable UI element. Just like basic JSON format, these are covered in the CUGL Scene Graph Tutorial. By breaking up your UI elements into separate widgets, you can greatly simplify your design process.

This assignment will require that you build off what you completed in the previous activity. If you did not finish that lab, contact a TA so that they can get you started. When you finish this activity, you will see the image below.

goal-scene

In addition, in the final task we will challenge you to make a scene of your own.

As a reminder, this assignment is graded in one of two ways. If you are a 4152 student, this is graded entirely on effort (did you legitimately make an attempt), and that effort will go towards you participation grade. If you are a 5152 student, you will get a more detailed numerical score.

Table of Contents


Useful Resources

Most of these resources from last time are useful for this activity. But since you are going to be editting JSON entries by hand, we want to re-emphasize the following resources.

JSON Tutorial

If you do not know what a JSON file is, then the above tutorial is an excellent resource for how to use them. As a designer, you do not need to know how to use JSON files in Javascript (or C++ for that matter). But you do need to know how to write and modify a valid JSON file with no errors in it.

For those of you who have taken Python, you will notice that JSON files look pretty much like (nested) Python dictionaries. And this is a helpful way to think about them. However, in Python all strings must have double quotes, not single quotes. Furthermore, all booleans are lower case (so true instead of True).

JSON Validator

When you write your JSON files, this files will have errors in them. And 90% of those errors will be the result of a comma: either a comma that is missing or an extra unexpected comma. While CUGL does provide you with error messages when this happens, it can be hard to read these error messages if you are not used to programming.

We cannot stress how important it is to use this tool while working on this assignment. While sometimes your mistakes will cause the SceneTool to produce a useful error message, other times you will simply get the dreaded “Segmentation Fault” (which means an unexpected error occurred).

CUGL Scene Graph Tutorial

Once again, this is a complete documentation of the JSON format used to create scene graphs. While this lab will take you through most of the process, you might want to keep this document around as a reference.

The Scene Tool

The scene tool that you will beusing is the same as it was for the previous lab. There have been no changes made to it. But if you have misplaced it, here are the links for the various platforms:

In both cases you run the application by typing the name SceneTool.exe. On some platforms you may need to add an extra ./ like ./SceneTool.exe.

IMPORTANT: On macOS, remember to validate the application by typing the command

xattr -cr SceneTool.exe

Widget Overview

A widget is a separate JSON file that defines a template for a UI template. We talked about these in the scene graph lecture. A widget is a separate JSON file that is stored in the widgets folder (not the json folder). It looks a lot like a scene graph with a few differences. Consider the widget below:

{
    "variables" : {
        "scale"   : ["data", "scale"],
        "texture" : ["children", "up", "data", "texture"],
    },
   "contents"  : {
        "type"   : "Button",
        "data"   : {
            "upnode"   : "up",
            "pushable" : [160,280,190,370,280,400,370,370,400,
                          280,370,190,280,160,190,190],
            "visible"  : false,
            "pushsize" : true,
            "anchor"   : [0.5,0.5],
            "scale"    : 0.8
        },
        "children" : {
            "up"       : {
                "type"   : "Image",
                "data"   : {
                    "texture"  : "play"
                }
            }
        },
        "layout" : {
            "x_anchor" : "center",
            "y_anchor" : "middle",
            "y_offset" : -115,
            "absolute" : true
        }
    }
}

The looks sort of like a scene graph JSON. Indeed, the contents of the contents object are a scene graph (in this case a simple button). The only thing that looks unusual is the variables object that comes before it.

To use a widget, you have to do two things. First you load it. Suppose the above widget is in a file called mywidget.json. Then you would need to add the following to the assets.json file, just before scene2s:

"widgets": {
    "widgetname" : "widgets/mywidget.json",
},
"scene2s" : ...

To use this in a scene graph, you would write something like the JSON below:

{
    "scenename": {
        "type"      : "Node",
        "format"    : {
            "type" : "Anchored"
        },
        "children":  {
            "button": {
                "type": "Widget",
                "data"   : {
                    "key"     : "widgetname"
                }
            }
        }
    }
}

What does this do? The game engine will take the contents object of mywidget.json and make it the contents of the button child. Hence if you have a UI element you are using over and over again, this cuts down on the amount that you need to write.

So what do the variables do? This is a form of customization. Right now the button uses the play texture for its image. What if we wanted to use a different image, called stop? There is a variable called texture, which we can set as follows:

{
    "scenename": {
        "type"      : "Node",
        "format"    : {
            "type" : "Anchored"
        },
        "children":  {
            "button": {
                "type": "Widget",
                "data"   : {
                    "key"     : "widgetname"
                    "variables" : {
                        "texture" : "stop"
                    }
                }
            }
        }
    }
}

This tells CUGL to go into contents, find the value texture, which is inside of data, which is itself inside of up, which is all inside of children, and replace the play with stop. This allows you to do minor reconfigurations like change a color or some text or even the size of a UI element. This is the type of thing you will be doing in this activity.


Instructions

We assume that you have completed the previous activity. In particular, you should still have your Figma project from that activity. Your Figma project should look something like the following.

figma-start

If you did not finish that lab, contact a TA so that they can get you started. You should not use your assets.json from that previous assignment. Instead, you will be using FigTree to help you make a new version of assets.json.

1. Create a Widget for the Scene

The file assets.json is going to contain a lot of information. As the name says, it is where you load all of your assets in the game. As such, it does not make sense to clutter it up with really complicated scenes. A better option is to take each scene that you design and make it a separate widget. That makes the amount of lines that you need to include in assets.json really small. It also means you can edit individual scenes without having to worry about messing too much up.

Fortunately, making a widget is really easy. The FigmaTree supports this as well. Take you project and go to developer mode. Look for the line that says code, with tree to the right. To the right of that, there should be a symbol with two vertical lines with knobs at different heights. Select that to get the menu below.

figma-output

You want to change the output format from Scene Node to Widget. Once you do that the JSON under FUGL Scene will look something like this.

{
    "variables": {},
    "contents": {
        "type": "Node",
        "format": {
            "type": "Anchored"
        },
        "data": {
            "anchor": [
                0,
                0
            ],
            "size": [
                1280,
                720
        
  Show 727 more lines of code

See the "variables" and "contents"? That is how you know this is as Widget. While the variables are empty, that is okay. Widgets are not required to have variables, especially if you do not plan to customize them.

Go into the widgets folder of SceneTool (you did download a copy of the new SceneTool, right?) and make a new file called labscene.json. Copy all of this JSON into this file.

Next open up the new assets.json, which should look like this.

{
    "textures": {
    },
    "fonts": {
    },
    "widgets": {
    },
    "scene2s" : {
        "lab" : {
            "type"      : "Node",
            "comment"   : "This is the root node of the scene for the lab",
            "format"    : {
                "type" : "Anchored"
            }
        }
    }
}

Remember to update the "textures" and "fonts" entries just like you did in the previous activity. CUGL will not know how to find your images and fonts without this.

This time you also want to update the "widgets" entry to add your new widget. Add a reference to the file you just created, like this:

"widgets": {
    "labscene" : "widgets/labscene.json"
},

Finally, you need to turn the lab scene into a Widget. Change the "type" from "Node" to "Widget". Finally, right after "format", add the following.

"data" : {
    "key" : "labscene"
}

Remember to add a comma after the closing curly brace of "format" and before "data". If you do not do that, this will not be proper JSON, and it will crash.

Once you do all that, it is finally time to run the SceneTool. You should be able to see the familiar scene from the previous activity. In addition, all of the buttons should work the same as last time.

2. Create Button Widgets

See those three grey buttons labeled "First", "Second", "Third"? They are all very similar, which means that they have a lot of redundant JSON. We are going to factor them out as widgets.

Again, we are going to take advantage of the Figma plugin. Go to developer mode, and then move over to the layers panel on the left. Instead of selecting root, we want you to expand the layers and select button:button1. Now in the developer panel, you should see something like this.

{
    "variables": {},
    "contents": {
        "type": "Button",
        "format": {
            "type": "Anchored"
        },
        "data": {
        "anchor": [
            0,
            0
        ],
        "size": [
            240,
            60

  Show 110 more lines of code

In the previous activity, it never made sense to select a sublayer, because we always wanted the entire scene. But now that we are making widgets, it does.

Make a new file called textbutton.json in the widgets folder. Copy all of the JSON into this file. Then go to assets.json and add this entry to the "widgets" group. Remember to have a comma between entries in this group.

Finally, we want you to turn the first button into a widget. Search for "button1" in labscene.json. You are going to need to make some changes to this entry. As a general rule, you want to keep everything after "layout" as that information information comes from the parent, not the widget. But the entries "format", "data", and "children" can all be deleted (as that information is stored inside the widget). Instead, the "data" entry should be replaced with the following.

"data":  {
    "key": "textbutton",
},

Do that, and load up the SceneTool. You should still see your original scene unchanged.

Now, do the same for the other two buttons: "button2" and "button3". When you are done, you should get a scene that looks like this:

task-2

3. Add Button Variables

Before we had three different buttons. But now they all look the same. That is because they are using the same widget. But that is okay; we just need to add some customization features to the widget.

Replace the "variables" entry at the top of textbutton.json with

"variables" : {
    "text"   : ["children","up","children","text","data","text"],
}

How do we interpret this? Look at "contents". In that group there is an entry called "children". Inside that entry is another one called "up". That has some children, one of which is called "text". That node has some data, with the value "text". That "text" is currently assigned to "first".

We are telling CUGL that we want that value "first" to be replaceable. We are creating a variable called "text" and specifying how to reach it in the contents section. We did not have to call this variable "text". We could have called it "word" or something else if we wanted to.

Now go back to labscene.json. For the entry for "button1" change the "data" entry to the following.

"data":  {
    "key": "textbutton",
    "variables" : {
        "text" : "first",
    }
},

This tells the widget we want to use "first" as our text in the button. Do the same with "button2" and "button3" but make the text "second" and "third", respectively. Now when you run the SceneTool you should see your original scene with three different buttons.

Before moving on to the next step, we want you to add one more variable to textbutton.json. Add one for ["data", "size"], the size of the button itself. It can be named anything you want.

4. Add a Nine-Patch

When we made the buttons, we had a grey background that was exactly the right size of our button. But that will not always be the case. As we will see later in class, image files take up a lot of memory on the computer, so you want to minimize the number of them. You would like to have a single background that works for buttons of various size.

Go into assets.json and look at the "textures" entry. Replace greybutton.png with greynine.png. This is a much more compact image designed to be used for buttons. Launch the SceneTool again. You should see the following image.

task-4a

Note that if your image does not look like this, it is possible that you never set the constraints on the button backgrounds to left-and-right/top-and-bottom like we told you to. That contraint is necessary to get an image to fill the entire node. To fix this, you can go into textbutton.json and find the "greybutton" entry. Find the "layout" values of this entry and change both "x_anchor" and "y_anchor" to "fill".

Even if it is working correctly for you, this obviously not want we want. The problem is that CUGL is stretching the image to fit and this is distorting the corners of the rounded rectangle. How do we fix this?

The solution is to use a nine-patch, which is an image that can be resized without distortion. Nine-patches work by breaking the image into nine quadrants. The corners do not stretch at all. The sides only stretch along one access. And the intertior stretches in all directions. To define a nine-patch, you need only take an image and define its interior. The other quadrants will be computed automatically from that. The file "greynine.png" in textures is a nine-patch image, and its interior is as shown by the dotted blue box below.

ninepatch

Creating a nine-patch is just like creating an image node. The only difference is that you have a data property for the interior, which is the box show above. The interior for this particular image is defined by

"interior" : [27,29,42,40]

The first two values are the x and y values of the bottom-left corner of the interior. The second two values are the width and height of the interior, respectively. All values are in pixels (because you are referring to a file and not a scene).

When you create a nine-patch you must also define the size. The only exception to this is if the "x_anchor" and "y_anchor" values are both "fill". In that case it expands to fill its entire parent. That is why it was important for you to set this constraint in Figma.

To turn the buttons into nine-patches, go into textbutton.json. Find the entry for "greybutton". Set the type to "Ninepatch". Finally, add the interior to the "data" group. When you are done, you will see your original image once again.

Now it is time to show off the power of nine-patches. In the previous step we asked you to add a "size" variable to your widget. Go into labscene.json and set this variable for each of the buttons. The first button should have size [220,60], the second [240,60], and the third [260,60]. When you are done, run the SceneTool. You should get the following image.

task-4b

5. Add a Slider

It is now time to add a new UI element to your scene. This time we are going to add a slider with a knob. This is a common UI element, especially on mobile devices. This element is not supported by the Figma plugin, so it will require some manual JSON editting. However, it is possible to mock up the slider first, to minimize the JSON that you need to edit.

In the latest version of the SceneTool, there are two new images in the textures folder: knob.png and slot.png. Place both of these into your Figma scene (named knob and slot, respectively). They should be under the root frame, but not under any other layer. The slot image is going to represent the track of your slider. Frame that image and rename that frame slider. Put the knob image inside this frame as well. The position both of them below the menu, as shown below.

figma-slider

The constraints on the slider frame should be Center/Bottom, as we want to anchor it to the bottom center of the screen. The other constraints are less important, but Center/Center is alway a good idea if you are unsure.

It is time to add this slider to CUGL. However, you have spent a lot of effort modifying this scene, so it would be a pain to have to go through all those steps again. Once again, you are going to use widgets to solve the problem. Create a new widget slider.json. Select the frame slider in the layer panel and copy the widget code into the file.

When copying the widget to a file, you will need to make one minor change. The anchor of the slider (on line 9 is not quite right. Figma only sets anchors when a node is a child of a parent. In this case the slider is the root, so the anchor was set to [0,0]. We want the slider centered, so the anchor needs to be set to [0.5,0.5] (and indeed this would be the case if we selected root instead).

You now want to place the slider in the scene. Go into labscene.json and find the very first entry for "children", which are the immediate children of the entire scene. The first entry should be for "background", which is the background image. We always want that to the first child, as children are drawn back to front. But the slider can be the second child, immediately after it. Add this entry between "background" and "startmenu".

"slider" : {
    "type" : "Widget",
    "data" : {
        "key" : "slider"
    }
},

Also, remember to add the slider.json to the list of "widgets" at the start. Run the SceneTool and you should see the slider. It will be a static image and not do anything yet, but it will be there.

However, it will not play well with the changes in aspect ratio. That is because it is missing the layout information. Widgets do not have layout information, because that is assigned by the parent. To find the layout information, select the root in developer mode, as that is the parent of the slider. Copy the JSON code into another blank file so that you can search it. Look for "slider" in this file, and find the layout information. Copy "layout" and everything after it into assets.json in the correct place. Remember to check your commas before running SceneTool. Now the image will work correctly at different aspect ratios.

Of course, the slider is still a static image. We need to turn it into an actual slider. Once again, we will need to modify some JSON. Fortunately, this can all be done in slider.json, which is a much smaller file. As a first step, change the type of this widget to "Slider". But if you run the SceneTool now, it will crash. That is because the slider needs some extra information in "data" to work properly.

In particular, a slider must have the following entries:

  • "bounds": The location of the track in the slider
  • "knob": The knob image
  • "path": The slot image

Of these, "bounds" is the most important. It specifies the line for the slider track. The knob will always stay on this line. The line is specified as a rectangle [x,y,width,height]. The line starts out at (x,y) in the slider coordinate space, and goes to the opposite corner of the rectangle. So, if you want a horizontal slider 100 units long starting at (20,50), you would specify [20,50,100,0]. Similarly, [20,50,0,100] would create a vertical slider from the same starting point.

We want these "bounds" to line up with the slot image that we placed. If you look at this image in a graphics program like photoshop, you will see that the left side of the slot interior is a rounded areay centered at (18,18) while the right side is centered at (382,18). This means that a bounds of [18,18,364,0] makes the most sense.

Add that value to "data" and run the SceneTool. You should see something like the following image.

task-5a

What is going on here? You did not tell CUGL that you had images for the knob and path, so it created them for you. It created some crude images using the geometry tools in CUGL. Not particularly pretty, but they get the job done. With that said, this is useful as a first attempt, as it show you where the slider will actually be positioned once you hook up the images.

It is now time to add the images. Assuming you named the Figma layers knob and slot, add the following values to "data"

"knob" : "knob",
"path" : "slot"

Run the SceneTool again. Now you will see the following.

goal-scene

Touch the knob and move it. You now have a working slider!

Sliders have several other properties that are not visually apparent, but are important to your programmers. Whenever you slide the knob about, it produces a value. The value is where it is along the track. But this value is not a position. Instead, the slider has an associated "range" which is a min and a max value. If the slider is all the way to the left, it is the minimum value and if it is all the way to the right it is the maximum value. By default the range is [0,100], but you can change this in the JSON.

You can also change the initial value of the knob. By default, the knob always starts in the middle of the range. But if you assign a "value" in the JSON it will put the knob there initially. Set the value to 25 and watch what happens.

Sliders have many useful settings, including snapping to tick marks. All of these are all covered in the scene graph tutorial. You should explore these on your own time.

6. Create Your Own Scene

Up until now, you have been creating an scene that we designed for you. It is now time for you to show off everything you have learned. Make us a new scene! The scene should be a widget so that we can swap it out with labscene.json when grading. You should add this widget to the "widgets" section of assets.json.

The scene should use new images and a new font. Make sure that you have the appropriate rights to use the font (Helvetica is not okay for embedding into a mobile game!). The website dafont.com is a good source of free fonts. If you do not make your own images, make sure they are properly sourced as well.

This scene should have buttons and a textfield. Remember that you can make a textfield in Figma by tagging an text object with edit:. You will need to give the textfield a fixed width and height so that there is enough space for typing. See the discussion in the last activity for more information.

Beyond that, the scene is up to you. Impress us!

Submission

Due: Sun, Feb 23 at 11:59 PM

This activity is a little more work than the other two. That is because we had to build up your skills to get here. But this activity should be very doable for anyone who completed the previous one. Unlike that activity, this time you modified more files than just assets.json. So this time we want you a zip file containing the following:

  • The file assets.json
  • Everything in widgets
  • The new image file(s)
  • The new font file(s)
  • A README.txt describing the purpose of your new scene

Submit this zip file as lab3design.zip to CMS