Facebook Twitter YouTube Frictional Games | Forum | Privacy Policy | Dev Blog | Dev Wiki | Support | Gametee


Thread Rating:
  • 3 Vote(s) - 3.67 Average
  • 1
  • 2
  • 3
  • 4
  • 5
GUI Tutorial Series
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#1
GUI Tutorial Series

Lesson Zero

Welcome to my tutorial series on how to create spectacular terminal GUIs in SOMA. I'm striving to make this tutorial as easy to follow as possible, so I won't assume any previous experience with the ImGui system. I do, however, assume that you have already familiarized yourself with the general modding toolkit in SOMA. This tutorial will also largely take place in the script-side of things, so I expect that you know how to set up a SOMA workspace in CodeLite, if you have not already done so. (Here is a refresher for those that don't.)

If at any point you get confused about something during the course of the series, each tutorial will have downloadable files that comes with it. These files will contain the map as it should be at the start of the tutorial, as well as once the tutorial has been completed.

Now, the best place for any tutorial series to start is at the beginning, so this first tutorial will be on how to create a terminal on your map and prepare it for GUI scripting.

Table of Contents
The Basics
Getting More Advanced
Using SOMA's Built-In GUI Styles
  • StationGui (WIP)
  • UrbanGui (WIP)
  • Playing Audio (WIP)

Going Beyond Terminals
  • Setting Up a User Module (WIP)
  • Basic Heads-Up Display (WIP)
  • Target Info Module (WIP)
  • Player HUD Menu System (WIP)

Tutorial Requirements

For this tutorial, you will need the following:
  • A map with three things: a PlayerStartArea, a basic plane (of any material) for the player to stand on, and a light source with which to see.
  • A CodeLite workspace setup, linked to your map's script file.

Tutorial Source Files

Initial File
Completed File

The Tutorial Itself

First, place a terminal entity onto your world. It can be anywhere, really, as long it's in a place that the player will be able to easily see it.

The type of terminal doesn't particularly matter either, but the large computer panel terminal is nice and big, so it will be an ideal choice. Be warned, there are several entities that are associated with a large computer panel, so make sure you get the one named "computer_panel_large_GUI". (You'll know it's the right one if you select it and see a Terminal section under the Entity tab.)

[Image: zNlYTwR.png]

Now open the Terminal section up. From here, you will see a number of text boxes, check boxes, and number boxes - just a lot of boxes, really. For now, though, the only one we will be worrying ourselves with is the box at the top labelled "OnGuiFunction".

[Image: DFYX1lz.png]

This box will hold the name of the callback function that will be handling all the GUI drawing of our terminal. Strictly speaking, you can call it whatever you want, but good practice is to call it the name of your terminal entity followed by "_OnGui".

[Image: HnEby2F.png]

That's all we need to do in the Level Editor for now, but before we leave, we are going to want to click the button under the text box marked "Copy". What this does is it copies the relevant code for your function into your clipblard, so you don't have to worry about remembering exactly what you need to type into your script file. Don't forget to save at this point!

Now let's head over to our script file. If your map is freshly made, your script file should be full of all the default code from when the file was generated. Scroll all the way down to the bottom of the file, and you will see a section marked out specifically for terminals.

You should still have the code in your clipboard from before, so go ahead and paste it under the section marked for your terminal functions. (If you've lost the code from before, you can always go back to the Level Editor and copy it again.)

[Image: NccxncV.png]

After a little bit of formatting, your code should look like the above picture.

What you are looking at is the default callback function for a terminal's OnGui event. The first parameter is a string called "asEntityName", which will hold the name of the terminal entity that is invoking this function. Unless you want to have more than one terminal referencing the same function, you generally don't need to worry about this parameter.

The second parameter is a float called "afTimeStep". This parameter holds the length of time that has passed since the last time OnGui was called. This is useful to have if you will ever be creating terminal GUIs that deal with animated elements, such as the waveform terminal in the pilot seat room in Omicron, but otherwise you probably won't have to bother much here either.

And that's it, really. As of this point, you now have a static terminal that does absolutely nothing. After you've finished celebrating, we can move on to the next tutorial, in which we will get our terminal to do some basic stuff.
(This post was last modified: 09-03-2016, 08:05 PM by Abion47.)
11-07-2015, 09:27 AM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#2
RE: GUI Tutorial Series

Hello, End of the World

Alright, now that we have ourselves a working terminal, it's time for us to make it actually do something.

Table of Contents
The Basics
Getting More Advanced
Using SOMA's Built-In GUI Styles
  • StationGui (WIP)
  • UrbanGui (WIP)
  • Playing Audio (WIP)

Tutorial Requirements

For this tutorial, you will need the following:
  • A map with a prepared terminal, plus all the necessities.
  • A script file with a prepared OnGui terminal callback function.

Tutorial Source Files

Initial File
Completed File

The Tutorial Itself

Assuming our terminal is properly configured (which it should be if you just got here after completing the last tutorial), you won't need the Level Editor at all for this one. Go ahead and stow it to the side for now.

Alright, we've got ourselves a shiny new terminal, but what do we do with it? Well, as anyone who's ever taken a programming course before will know, the first thing to do is "Hello World". So what we want to do now is to make some text appear on our screen. There are a few ways to go about this, but the simplest way is to use a Label. To use a Label, we use the function ImGui_DoLabel, which comes from the helper file "helper_imgui.hps" (it should be included at the top of your script file by default, but sometimes it doesn't hurt to double-check).

Open up your script file (assuming it isn't open already), and scroll to the OnGui function of our terminal. Inside the curly braces, type the following code:

ImGui_DoLabel("Hello, Apocalypse!");

What this code does is it tells the terminal's Immediate GUI handler (or ImGui for short) to render a Label with the text "Hello, Apocalypse!". A Label is the simplest form of writing text, as it just spits out whatever you want it to say, no questions asked.

And that's it! Save your script, then load your map into the game using the dev launcher. If all is well, you should see something similar to the following images.

[Image: nDuMsXJ.jpg]
[Image: djGkZFH.jpg]

Aw, isn't that just adorable? But hey, at least the terminal is showing something. That's a lot more than it was doing five minutes ago. Still, it would be a bit more impressive if we could get that text somewhere less... disappointing.

Let's go back to our script and make a quick adjustment. Change the previous line of code to say this:

ImGui_DoLabel("Hello, Apocalypse!", cVector3f(220, 260, 0));

This should look mostly familiar, but now instead of giving our function one parameter, we've given it two. That second parameter, a cVector3f, tells ImGui to put our text at a particular position on the terminal screen, which in this case is at the point [460, 394], which for this terminal puts it to the left and a bit up from center of the screen. When we don't specify this position, ImGui defaults to putting our stuff at the point [0, 0], which is at the screen's very top-left.

Save your code, and reload your map in the game.

[Image: fp91x7a.jpg]

We are starting to get somewhere, but that text is still way too small. We're going to have to get that text to grow a little bigger somehow.

Let's revisit our code again. What we need is a way to change the size of our text. To do that, we're going to need to use a special type called cImGuiLabelData. We're also going to have to change up how we are drawing our Label a bit.

cImGuiLabelData labelData;
labelData.mFont.mvSize = cVector2f(100, 100);

ImGui_DoLabelExt("Hello, Apocalypse!", labelData, cVector3f(220, 260, 0));

Well, that was a lot of new-ness real fast, wasn't it? Let's step through this code to see what each thing does.

First off is our new type, cImGuiLabelData, which we declare into the variable called "labelData". This type is what handles all the customizable properties we want to set with anything regarding a Label, including size, color, or even if we want to draw a background image behind the text.

The next line may look a bit confusing, but what's going on is that labelData has a field within it called mFont that holds all the properties regarding specifically the Label's text. In this case, we are assigning a new size to mFont's mvSize field, specifying that we want some font that is appreciably larger than what we had before.

Finally, we are using a different way to get ImGui to draw us a Label, called ImGui_DoLabelExt. What the "Ext" part of that means is that, instead of ImGui to refer to its default values to the Label properties, we want to supply our own values instead. We do this by passing in our labelData as the second parameter, shifting the position vector over a bit.

Now that we got this code in, let's see what we got.

[Image: Hz6wnGF.jpg]

Now that's what I'm talking about!

We've got our text doing what we want it to do now, which is provide us some information in a way that doesn't make us go prematurely blind from all the squinting we would have to do. In the next tutorial, we're going to ramp it up a bit, tackling a few other widget types to get our terminal looking real spiffy.
(This post was last modified: 09-03-2016, 08:05 PM by Abion47.)
11-07-2015, 10:44 AM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#3
RE: GUI Tutorial Series

Your Friendly Infomercial

Labels are good and all, but in terms of displaying text, they can be a bit limited. Also, what if we wanted to do something other than just simple text? This tutorial will introduce you to two new widgets - the TextFrame and the Image.

Table of Contents
The Basics
Getting More Advanced
Using SOMA's Built-In GUI Styles
  • StationGui (WIP)
  • UrbanGui (WIP)
  • Playing Audio (WIP)

Going Beyond Terminals
  • Setting Up a User Module (WIP)
  • Basic Heads-Up Display (WIP)
  • Target Info Module (WIP)
  • Player HUD Menu System (WIP)

Tutorial Requirements

For this tutorial, you will need the following:
  • A map with a prepared terminal, plus all the necessities.
  • A script file with a prepared OnGui terminal callback function.

Tutorial Source Files

Initial File
Completed File

The Tutorial Itself

We are tasked with making a professional-looking promotional display showing off the latest and greatest in technology. For some reason, someone higher up thought to give us the job, but hey, at the end of the day, a pay check is a pay check, right?

Let's start with a nice big header that will pop out and grab any passing pedestrian's eye. To do this, a Label would be just perfect, so let's set one of those up real quick.

cImGuiLabelData labelData;
labelData.mFont.mvSize = cVector2f(100, 100);

ImGui_DoLabelExt("Obligatory Omnitool", labelData, cVector3f(150, 90, 0));

As you can see, this is pretty similar to what we've already done in the last tutorial, with the only difference being a tweak to the position.

[Image: bmq6Uo9.jpg]

Next, we're going to need an image to show potential customers what it is they've actually been missing with their lives. To do this, we're going to need to introduce a new widget, appropriately named the Image.

Now unlike text, an image requires a bit of extra work to get done. When you pass ImGui an image file, it prefers that the file has already been pre-loaded into memory. The best place to do this is when the map first runs. Scroll up to the OnEnter function of your map script and stick in this code:

void OnEnter()
{
    ImGui_PreloadImage("inv_omnitool.tga");
}

What the ImGui_PreloadImage function does is it takes an image file and pre-loads it into the game. That way, when you give your image to ImGui, it doesn't need to bother with loading the image on the fly - it can just go and pull it from memory.

Now that we have that out of the way, it's time to actually draw your image. Head back to the OnGui function and put this code in after our Label code.

ImGui_DoImage(cImGuiGfx("inv_omnitool.tga"), cVector3f(530, 280, 0), cVector2f(500, 475));

As you may have noticed by now, the way the ImGui functions that deal with drawing widgets to the terminal follow a particular naming scheme. This function, ImGui_DoImage, takes a new form of widget data called cImGuiGfx as its first parameter. Like cImGuiLabelData, this type handles the customizable properties of a graphics asset, which in this case is an image file.

The second parameter should look familiar, as it's the same as what happens ImGui_DoLabel - you pass in a cVector3f to tell ImGui where the image needs to go. But it also has something new in the form of the cVector2f in the final parameter. This tells ImGui what size the image should be. If you don't specify this, it will default to the image file's physical size, which in this case would make the image quite small.

When you get that code put into place, it should look something like this:

[Image: 38Hgwhs.jpg]

Alright, we've got the eye-catching image and the attention-demanding title, but now we need some informational text to give the reader some more information about what it is they are looking at. We could use Labels again, but Labels only deal with one line of text at a time, which is fine for a brief snippet, but doesn't really work for a paragraph. What we need is a widget that will automatically wrap text around into multiple lines, and a TextFrame is just the widget for the job.

Underneath your Image code, put in the following. (It's a bit of a mouthful, so if you copy-paste this one I'd understand.)

cImGuiTextFrameData textFrameData;
textFrameData.mFont.mvSize = cVector2f(35, 35);
textFrameData.mColorBase = cColor(0, 0, 0, 0);

ImGui_DoTextFrameExt(
    "This is an Omnitool. Look at it's majesty. I bet you wish you had one. Well, unless you also want to live in a future world where robots and mutants comprise the only remaining lifeforms and the entire planet burns with the fire of a thousand evils, then I don't think you want one after all.",
    0,
    10,
    0,
    textFrameData,
    cVector3f(140, 340, 0),
    cVector2f(480, 360));

That's quite a lot of code! But don't worry, we can break it down into digestible chunks.

First up, we've got our widget data in the form of cImGuiTextFrameData, which holds the properties of a TextFrame. Like the Label, we set our font size so that we can actually read our text from further than two inches away. Unlike the Label, however, TextFrames come with a background color, which defaults to white. Incidentally, the TextFrame's text color also defaults to white, which is a bit of a problem. To compensate, we change the background to a nice fully transparent black.

Then we have the ImGui's draw function for TextFrames, the ImGui_DoTextFrameExt. The first parameter is the text that will actually go into the TextFrame, so that's self explanatory.

The second parameter is the amount of space the TextFrame will reserve around it's edges that it will not draw text into. (This is also called the "padding", for you web developers out there.) Because we aren't doing anything too fancy with the TextFrame, we can leave it at zero.

The third parameter is the amount of space between individual lines. Leaving this one at zero leads to potentially difficult to read text, so we'll throw in some breathing room.

The fourth parameter is the amount of extra space given to the first line of text. This is useful for if you're going for a sort of an essay feel and want to give your paragraphs a leading indentation. It's not really necessary for our purposes, though, so we'll leave it at zero as well.

The remaining parameters are what you've already seen so far - the widget data, the position vector, and the size vector. You should pay particular attention to the height of your TextFrame, as it will effectively clamp the number of lines in your text. If your text is too long, then any text after the cut will become lost.

Right, now that we've worked out what this code does, let's give it a go.

[Image: h3hwDk7.jpg]

That looks like a beautifully crafted advertisement if I've ever seen one. People would be absolutely mad to not want an Omnitool after seeing this bad-boy in action.

Now that I think about it, though, remember that massive line of text in the code from the TextFrame? It sure makes your code look a bit ugly. And what if you had to change the text in the middle of a big project... and you had to hunt down that one line to change it, only to find out an hour later that you need to change it again?

That sounds like a lot of headache and grief just waiting to happen. I wish there was a better way to organize the text of your terminals in a way that didn't involve sifting through rivers of code to change one letter...

Let's discuss that one in the next tutorial, shall we?
(This post was last modified: 09-03-2016, 08:05 PM by Abion47.)
11-07-2015, 11:11 AM
Find Reply
Kanthos Offline
Junior Member

Posts: 48
Threads: 7
Joined: Oct 2015
Reputation: 1
#4
RE: GUI Tutorial Series

Abion, i love you. Big Grin

thanks for this
11-07-2015, 12:38 PM
Find Reply
RaideX Offline
Member

Posts: 212
Threads: 33
Joined: May 2013
Reputation: 7
#5
RE: GUI Tutorial Series

Nicely written Tutorial Smile

A quick thing I've noticed is that after you declare

cImGuiLabelData labelData

you call the variable "fieldData". I'm not sure if this is intentional but I just wanted to point it out.

If you don't draw first, you don't get to draw at all... -The False Shepherd
11-07-2015, 01:28 PM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#6
RE: GUI Tutorial Series

(11-07-2015, 01:28 PM)RaideX Wrote: Nicely written Tutorial Smile

A quick thing I've noticed is that after you declare

cImGuiLabelData labelData

you call the variable "fieldData". I'm not sure if this is intentional but I just wanted to point it out.

Not especially intentional, no. Tongue

The next tutorial(s) will go up once I get some shut-eye.
(This post was last modified: 11-07-2015, 02:53 PM by Abion47.)
11-07-2015, 02:52 PM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#7
RE: GUI Tutorial Series

Mind Your Language

This tutorial isn't strictly about GUI development, but it covers a very important related topic. In any form of programming, it is important to keep your data and logic seperate. What this means in a nutshell is that you should code in such a way that changing one thing that your program does shouldn't involve an overhaul of how it does it. GUI development in SOMA is no different in this regard.

Table of Contents
The Basics
Getting More Advanced
Using SOMA's Built-In GUI Styles
  • StationGui (WIP)
  • UrbanGui (WIP)
  • Playing Audio (WIP)

Going Beyond Terminals
  • Setting Up a User Module (WIP)
  • Basic Heads-Up Display (WIP)
  • Target Info Module (WIP)
  • Player HUD Menu System (WIP)

Tutorial Requirements

For this tutorial, you will need the following:
  • A mod with the basic needs to be run through the ModLauncher.
  • A map with a prepared terminal, plus all the necessities.
  • A script file with a prepared OnGui terminal callback function.

Tutorial Source Files

Initial File
Completed File

The Tutorial Itself

Remember in the last tutorial how we made a TextFrame with a paragraph's worth of data? Holding that much text in your script files is bad form. For one, like I said above, it can get very tedious when you need to modify something small but need to sift through all your code to find it.

How to put your maps and such into a complete mod is outside the scope of this series, but this tutorial is going to require that your mod be able to get loaded by the ModLauncher. As such, in the Initial File I provided, I've included the complete mod setup in addition to the map files. You can use that to get started, if you don't want to bother setting it all up yourself.

What we're going to first, though, is set up a TextField in our map with a couple paragraphs of text. You know, I've gotta tell you how to do it wrong before I show you how to do it right. Or something like that.

Open up your map script file, go down to the OnGui function, and slap in the following code to create the TextField we will spend the rest of this tutorial fixing.

// Label
cImGuiLabelData labelData;
labelData.mFont.mvSize = cVector2f(100, 100);

ImGui_DoLabelExt("Example of too much text", labelData, cVector3f(80, 20, 0));

// TextFrame
cImGuiTextFrameData textFrameData;
textFrameData.mFont.mvSize = cVector2f(45, 45);
textFrameData.mColorBase = cColor(0, 0, 0, 0);

ImGui_DoTextFrameExt(
    "This is a lot of words. As you can see, we don't like putting this many words into our code field. It makes the code cumbersome and it hurts the developer's ability to maintain it. It would be better if, whenever we had to put text at all into a separate file altogether, and write our code to pull it out of that file. If we did this, then we could easily change the text whever we wanted without changing the code at all.",
    0,
    10,
    0,
    textFrameData,
    cVector3f(80, 240, 0),
    cVector2f(880, 660));

It's quite a bit of code, for sure, but's nothing you haven't seen before.

If we were to run our map through the DevBat right now, it would pretty much appear exactly how we would want it to.

[Image: u7qJsUl.jpg]

But then again, making the GUI look perfect isn't what we are after here. We are going after the goal of making our code look pretty.

What we are going to do now is take all of the text we just hard-coded into our script file and move it into what is known as a "lang file", which is short for "language file". Putting all of our text resources, be they GUI text, subtitle text, or any other text at all, into a lang file, we make it so all of our text is in a single location, which the rest of our mod will able to reference from anywhere else. This makes our mod efficient, which in turn makes it easier to code.

Another benefit to lang files is if you ever want to offer translations of your mod in different languages, lang files allow you to do so without changing any of your code. Enabling this behavior is beyond the goals of this particular tutorial, but if the interest is there, I can explore the topic in the future after all the basics are accounted for.

Go into your mod files (which I will work under the assumption is the files that I provided above). Under the root directory "GuiTutorial04", there should be a folder called "config". In this folder should be three files. What we will be using is the file called "english.lang". Go ahead and open it in the text editor of your choice.

A lang file is essentially an XML file, so there isn't any complicated syntax to learn here. All you need to know is how a lang file is structured.

<LANGUAGE>
    <RESOURCES>
        <Directory Path="fonts/" />
        <Directory Path="lang/eng" />
    </RESOURCES>
  
    <CATEGORY Name="ExampleCategory">
        <Entry Name="TextEntry">This is the text inside this entry.</Entry>
    </CATEGORY>
</LANGUAGE>

First, you have the resources at the top under the "Resource" tag. In this case, the resources are already be completed in the lang file for you, and typically you won't ever have to mess with this.

Second you have your categories. Each of these categories represents a different "section" of your mod. Each section will typically be a particular map, if your mod spans multiple maps. It can also be a section of your GUI, if your GUI is going to be overly complex.

Finally, within the categories, you have your entries. Each entry stores a particular chunk of text that your code will be retrieving.

As you can see, categories and entries both have an attribute called "Name". This is what your code will be using to actually get the text, so it's important that each category you have is given a unique name, and each entry within a given category has a unique name. (While it is allowed to have two entries within different categories to have the same name, it is advised to not do this for the sake of your sanity.)

Now that we have the explanations out of the way, it's time to actually write us some language.

Near the bottom of your language file, just above the "</LANGUAGE>" tag, let's create a new category. Type in the following text:

<CATEGORY Name="GuiTutorial04">

This line creates a new category section named "GuiTutorial04". Every entry you create in this category will be tied to the category, so you will need to use this name in your code when you need those entries. As such, it's a good idea to name your category something easy to read and remember. (For example, "Office_Laptop_Terminal" is a better category name than "GUI_001_A4".)

Enter a new line after the previous code. It's time for us to create an entry. Let's start with the entry for our Label text:

<Entry Name="Label_Text">Example of too much text</Entry>

This creates a new entry with the name "Label_Text". Inside the "Entry" tags, you can see the text that we used earlier when creating our Label. We can use this entry name later on when we revisit our code.

We've got our Label entry, so now let's make our TextField entry:

<Entry Name="TextFrame_Text">This is a lot of words. As you can see, we don't like putting this many words into our code field. It makes the code cumbersome and it hurts the developer's ability to maintain it. It would be better if, whenever we had to put text at all into a separate file altogether, and write our code to pull it out of that file. If we did this, then we could easily change the text whever we wanted without changing the code at all.</Entry>

Just like above, we've made our entry called "TextFrame_Text" to hold the text that we used in our GUI code before.

Before we go back to our code, there's one more thing we have to do in our lang file. Put the following text in a new line after your entries:

</CATEGORY>

In XML, every tag you open must also be closed. Because we made our category above, we now have to close it. If you don't do this, the game won't necessarily cause errors to pop-up, so instead you might end up with bizarre bugs, and it won't be immediately obvious what is causing them.

Alright, that should wrap up everything we need to do in the lang file, so let's go back to our map script.

The first thing we need to do is tell ImGui that we want to be using a lang file as our text source rather than using the text we give it directly. To do this, put in the following code to the top of your OnGui function:

ImGui_SetTransCategory("GuiTutorial04");

What the function ImGui_SetTransCategory does is it tells ImGui that, for all the text widgets that we will be creating afterwards, we want ImGui to be using our lang file, and specifically the category "GuiTutorial04".

Now we just have to change up our Label and TextField creation code. Update the ImGui_DoLabelExt and ImGui_DoTextFrameExt lines of code to show the following:

ImGui_DoLabelExt("Label_Text", labelData, cVector3f(80, 20, 0));
// ...
ImGui_DoTextFrameExt(
    "TextFrame_Text",
    0,
    10,
    0,
    textFrameData,
    cVector3f(80, 240, 0),
    cVector2f(880, 660));

As you can see, instead of the text we want it to render, the Label and the TextFrame now show the names of their respective entries in our lang file. Because we set our category before with ImGui_SetTransCategory, those functions will now attempt to pull the text from our lang files with those entry names rather than treat the text we give them as raw data.

You can see how this already makes our code much easier to read. Instead of manually inputting the text, we now have ImGui referencing our lang file. This leads to much more organized code and an easier time developing in general. This is especially useful if you have a number of places in your code that you want to display the same text, so now you can have them all reference the same entry rather than typing it out every time, and updating them all if you ever change the text.

(If you ever want to explicitly tell ImGui to treat the text you give it as raw text, use ImGui_SetTransCategory("").)

Alright, if we now run our map through the ModLauncher, you should see the following.

[Image: nDeBLPk.jpg]

You can see that there isn't really any visible difference, but that's okay. the fact that we now know our code is efficient and organized is plenty reward enough, let me tell you.

Now, the reason that I told you to run the map through the ModLauncher is because if you ran it through the DevBat, this is what it would look like:

[Image: w00rOq1.jpg?1]

There's nothing there! If you're wondering why, the error text at the bottom left will give you a clue.

[Image: SnVxUOb.jpg]

As you can see, the game says it can't find our lang file category. The reason for this is that when the DevBat loads your map, it doesn't load your lang file, so it will only be using the lang files for SOMA itself. And those lang files don't have our categories or our entries in them. (There are work-arounds for this of course, but they can get a tad complicated to set up, and are outside the scope of this tutorial anyway.)

That brings this tutorial to a close. From now on for future tutorials, I'm going to continue using raw text rather than lang files purely because from a tutorial series' standpoint, it's easier to follow raw text than having to frequently refer to lang files to see what the crap it is I am trying to say. Just remember, when you create your own mods, lang files are your friend, and they only want to help you succeed.

So far, our terminals have been little more than fancy over-complicated signs. In the next tutorial, we're going to look into how to make our terminals interactive.
(This post was last modified: 09-03-2016, 08:05 PM by Abion47.)
11-08-2015, 03:38 AM
Find Reply
A.M Team Offline
Banned

Posts: 811
Threads: 63
Joined: Sep 2014
#8
RE: GUI Tutorial Series

Would you like to make a WIP entry in the wiki? I think it would be most appropriate there. Wink
11-08-2015, 12:46 PM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#9
RE: GUI Tutorial Series

There's already a WIP entry in the wiki, and I didn't want to steal what had already been done or make a duplicate.
11-09-2015, 01:28 AM
Find Reply
Abion47 Offline
Senior Member

Posts: 355
Threads: 22
Joined: Oct 2015
Reputation: 46
#10
RE: GUI Tutorial Series

Who's Got The Button

Up until now, we've been making terminals that have been capable of drawing text in a variety of forms, as well as being able to draw images. This is great and all when you want to display some information quickly, but all the memorable terminals in SOMA involve the player actually being able to navigate around, performing actions, and taking names. So let's start swinging this series around and learn how to make our terminals interactive.

Table of Contents
The Basics
Getting More Advanced
Using SOMA's Built-In GUI Styles
  • StationGui (WIP)
  • UrbanGui (WIP)
  • Playing Audio (WIP)

Going Beyond Terminals
  • Setting Up a User Module (WIP)
  • Basic Heads-Up Display (WIP)
  • Target Info Module (WIP)
  • Player HUD Menu System (WIP)

Tutorial Requirements

For this tutorial, you will need the following:
  • A map with a prepared terminal, plus all the necessities.
  • A script file with a prepared OnGui terminal callback function.

Tutorial Source Files

Initial File
Completed File

The Tutorial Itself

The first thing we need to do is to head back to the Level Editor. When we created our terminal back in Lesson Zero, it's default setting was to disable player interaction. That's been fine so far, but we're going to need that interaction now.

Select your terminal entity, and under the Terminal tab, enable the AllowInteraction box.

[Image: JFkvSZF.png]

That's all we need here, so save your map and head over to the script.

So far, we've seen three types of widgets - the Label, the TextFrame, and the Image. Here, we are going to introduce the fourth basic widget, the Button. Down in your OnGui function, insert the following code:

cImGuiButtonData buttonData;
buttonData.mFont.mvSize = cVector2f(60, 60);
buttonData.mColorBase = cColor(0.35, 0.35, 0.35);
buttonData.mFont.SetFile("sansation_large_bold.fnt");
            
bool bButtonState = ImGui_DoButtonExt("gui_button", "Press Me", buttonData, cVector3f(150, 190, 0), cVector2f(300, 80));

Alright, let's take this one through the motions.

First up, you've got your button widget data, the cImGuiButtonData. Like the others, this holds the button properties that we are going to use later on.

The next couple of lines has the usual - changing the font size so our text isn't tiny, and changing the background color of the button. Like the TextFrame, both the button's text color and background color default to white, and that isn't condusive to a good GUI experience. Here, we change the color to a slightly dark gray.

This next line is a bit new though. What the mFont's function SetFile does is it changes what font we are using for the text. The default font is alright for doing text, but when you use this font for a button, it skews the button text toward the bottom of the button, potentially pushing it off the button altogether. So we want to change the font to something that correctly positions the text in the center of the button.

Finally we have our ImGui_DoButtonExt function. So far, the ImGui functions we've been using to create our widgets have been straight-forward, but the Button function is a bit special. What it does is it returns a boolean value representing whether the Button has been pressed. If so, it returns true, otherwise it returns false.

Now we've created our Button, but as you see all we've done is capture whether the Button has been pressed - we haven't done anything that would actually be triggered by the Button. Let's fix that by putting in the following right after our Button code:

if (bButtonState)
{
    cImGuiLabelData labelData;
    labelData.mFont.mvSize = cVector2f(100, 100);
    
    ImGui_DoLabelExt("You pressed it", labelData, cVector3f(150, 450, 0));
}

Nothing too fancy. We check whether the Button was pressed through use of the variable bButtonState, and if it's true, we have ImGui put up a Label saying we did so.

If you were to run this code right now, you would be able to see your button in all it's glory.

However, there's a problem. Clicking our button makes the Label appear, but only for a fraction of a second. The reason for this requires a bit of background on how ImGui works.

You may or may not have noticed by now, but our OnGui function is being called many times per second. On each call, we are creating our widgets repeatedly, and essentially building our GUI from scratch every time. This is by design, but it can be a bit of a difficult concept to wrap your head around. But the result is that when we press our button in game, ImGui registers that press in the frame that it happened and creates the Button accordingly.

However, when we get to the next frame, we leave the frame in which the Button press was triggered, so it doesn't get registered again, causing ImGui_DoButtonExt to return false. This is what causes our Label to blink in and out of existence.

What we are going to need is to store the fact that a Button press happened in a way that retains the Button press even after the OnGui function completes its rounds. To accomplish this, we are going to use a member variable.

Scroll up to the section in your code that reserves space for your terminal variables, and put in the following:

bool mbButtonPressed;

Now scroll back down to the OnGui function and change up the body of our if statement block into this:

if (bButtonState)
{
    mbButtonPressed = true;
}

Then finally, underneath that if statement block, let's add another one:

if (mbButtonPressed)
{
    cImGuiLabelData labelData;
    labelData.mFont.mvSize = cVector2f(100, 100);
    
    ImGui_DoLabelExt("You pressed it", labelData, cVector3f(150, 450, 0));
}

At this point, we have two different boolean variables storing the state of our Button, but where bButtonState stores whether the Button was pressed in this frame, mbButtonPressed stores whether the Button has been pressed at all. This second boolean is what we want when we are deciding if we are going to show our Label, so we move the code to create the variable into the second if block.

If we run our map now, it should look similar to last time. However, instead of our Label blinking, it will now stick around after the Button gets pressed.

[Image: GvRROIH.jpg]

This is just about perfect, but one thing is still missing. As you can see, our Button's appearance is quite static. Hovering the mouse over it or clicking it doesn't change what it looks like. In fact, if the Label didn't appear, we wouldn't be having any indication that it was doing anything at all.

To change this, we are going to need to add a bit more information to our Button data. Underneath the code where we set buttonData's font file, add the following:

buttonData.mbUseInFocusColor = true;
buttonData.mColorInFocus = cColor(0.25, 0.25, 0.25);
buttonData.mbUseTriggeredColor = true;
buttonData.mColorTriggered = cColor(0.1, 0.1, 0.1);

What we have here is that we are changing the Button's background color based on what we are currently doing with it.

When the Button is in focus, that means the cursor is hovering over the Button, but hasn't pressed it yet. We want to change the color in this event to confirm to the player that what they are looking at is a Button that can be pressed, so here we will set it to a slightly darker gray than before.

When the Button is triggered, that means the cursor has the button pressed. This is to tell the player that the action they performed is doing something, and we will set it to the darkest gray yet so it is easily differentiated from the earlier two grays.

Another important thing we are doing is setting the buttonData's mbUseInFocuscolor and mbUseTriggeredColor to true. What this does is tell ImGui that there are values in mColorInFocus and mColorTriggered that we want it to use. If you don't set the flags to true, any color information you put in those fields will be ignored, so don't forget to do that.

Let's go ahead and save our script, then check our map in the DevBat.

[Image: q5i4ls4.jpg]

As you can see, our Button now behaves exactly how we would expect a Button to behave. Hovering over it hames it dim in response, then pressing it gives feedback in the form of the triggered color as well as making our Label appear.

This marks the end of the first section of this tutorial series. You now know how to set up your terminal to receive player interaction, as well as draw the four basic widgets - Label, TextFrame, Image, and Button. Using a combination of these four widgets will enable you to create all of the common terminal interfaces.

In the next section, we are going to dive into some of the more advanced widgets and GUI concepts, so stick around for that. Smile
(This post was last modified: 09-03-2016, 08:04 PM by Abion47.)
11-09-2015, 02:57 AM
Find Reply




Users browsing this thread: 1 Guest(s)