Hey I really like this tutorial, especially about records and constants... not really sure why Blizz uses their own names like "Real" instead of "double" or "float" and "Record" instead of "Struct"... but yeah good stuff man. I don't use constants enough!
SC2 video tutorials: http://www.youtube.com/onetwosc

jaminv AuthorBasic Dialog, Introduction to Constants and Records
Introduction
In this first tutorial, I'll go over building a basic "icon button" dialog. The end results will look something like this:
The demonstration map created through this tutorial is attached to the bottom of this post.
It's real simple, but as you'll see its a very flexible dialog. This simple dialog also allows me to introduce some key concepts to dialog building without being distracted by a complex dialog.
The two most important tools to building dialogs (and they aren't immediately obvious), are:
Constants and Records
Let me tell you why.
Constants
Constants are your friends. Constants let you change every element of your dialog from one place, with ease. They take a minute to set up initially, but you will get that minute back the first time you go to change something... and you will change something.
Dialog code should never have numbers in it. It should all be constants and calculated variables. Really, there are only three numbers you should ever see anywhere near dialog code (or most code for that matter):
Any other time, you should be using constant or a variable. Besides the cases above, the only other time you should use a numeric literal is to initialize a constant.
If you're not already familiar with what constants are, they are basically just variables. They can be global or local, but constants are most meaningful when they are global. We will only use global constants. The only difference between a constant and a variable is that a constant's value can't change. This distinction protects you from accidently changing it in the future. Really though, variables would suit the same purpose, so long as they were all set in the same place. The point is to set a global set of numbers that will control how all your dialogs are displayed. An example:
Building a Few Dialog Constants
We're gonna start with a new map and jump straight to triggers. Delete the four steps inside the melee initialization trigger. Then, make a new folder and call it "Dialog". Although we'll only have one dialog (for now), this is good practice. Inside the Dialog folder, make another folder and call it "Dialog Constants".
Inside that folder, create a new integer variable. Call it "cDialog Border". Check off "Constant" and give it an initial value of 40.
Dialog Border will be the distance between the edge of the dialog and where we start drawing, as seen as 1 below:
Repeat the steps to create the other three constants above. Your map should look like this:
Calculated "Constants"
You can perform calculations inside a constant's initial value, but the calculations can only contain other constants. Also, it's best to stick to the basic math functions. I'm not sure what other functions may work in a constant's calculation, but I've found custom functions generally don't work (even if they other perform simple math). Generally, I find it's best to not worry about which things can and can't be constants, if it's a calculated value it doesn't need to be a constant. It'd be nice, but the editor is a little weird when it comes to these sorts of things.
At any matter, we need a couple of additional values, which will actually be variables and not constants. For the most part, however, we will treat them just like all the other dialog constants.
Later, when we want to calculate how big the dialogs will be, we'll actually need to add the dialog border twice. Since this is fairly common, I find it's best to calculate this value upfront. It's also best that it be calculated, in case you later decide to change the dialog border.
So create a variable in the dialog constants folder that looks like this:
The second value we need to know is a little harder to calculate. We want to center our icon (cIcon Size = 76) onto our button (cButton Size = 84). The end result is 4, but we want to calculate it in case we change one or both of our size constants.
Create another variable, cButton Inlay, and set up the initial value as follows:
Which is, an Arithmatic (Integer) with an Arithmatic (Integer) in each side. Then it can be set up as (cButtonSize / 2) - (cIcon Size / 2). It's not the most accurate formula, but for even numbers it's perfectly accurate.
Your map should now look like this:
and hopefully you now have a good understanding of what constants are, and are starting to understand how they might be used to build a dialog.
Records
I'm going to use records to store the data for our buttons. If you're not familiar with records, they are kind of hard to describe in the abstract. I think it's best to teach through demonstration. Just follow through and you should have a decent understanding by the time we are done.
Alright, so we have X buttons. The picture shows 6, but really it doesn't matter how many buttons we have. We want to save a little bit of information about each button somewhere so that we can use it to display the button. Specificially, we want:
So let's do it. Start by creating a folder inside the Dialog folder called "Unit Button". Inside it, create a new record, and call it "Unit Button Record".
Over on the right, add 3 variables:
Creating a record doesn't actually do anything, directly. It just defines a new type that can be used later. The advantage of records is that they can be used as arrays. So we're going to create a new variable inside our Unit Button folder, and call it "Unit Buttons". The variable is of type Record - Unit Button Record. The variable should also be an array. Make the size 10 for now. It can always be changed later.
You may have noticed that you can not set an initial value.
Initialize Our Record
We're going to initialize our record in two steps. First, we're going to build a action definition called "Add Unit Button". It will take three parameters, Spawn Unit, Icon, and Tooltip (the three variables inside our record). It will fill the next available slot in our array with those three values. It will then advance a pointer so that we can keep track of what the next available array element is. We'll then need to create another action definition in which we call Add Unit Button once for every button we create. We will call this second action definition from our Melee Initialization trigger.
Before we get two far ahead of ourselves, we'll need that counter variable to keep track of how many buttons we've added already. This variable will serve two purposes. As I said, we're going to use it as a counter to keep track of which array element is the next available one. Once we're done though, it's actually going to hold the number of buttons we added, so this will be useful when we go to draw the dialog. You'll see how this comes up later. For now, just create an integer variable called "nUnit Buttons" inside our Unit Button folder, and set it to 0. The lower case "n" at the front is a fairly standard abbreviation for 'number of':
Now, let's create a new action definition inside our Unit Button folder, and call it "Add Unit Button". This action definition will be fairly short:
As I said, we're just taking in three parameters, and using them to set our record variables. Notice how we use nUnit Buttons, and then add one to it.
The next step is really quite simple. Create another action definition inside our Unit Button folder, calling this one "Initialize Unit Buttons". In this function, we just call Add Unit Buttons a bunch of times:
We'll call this action from our Melee Initialization trigger, but we're going to wait a minute before setting that up.
A Word About Flexibility
It's important to mention that you don't have to have 6 buttons. You can actually have any number of buttons, but I chose 6 because I wanted there to be two rows, and I didn't want to spend a great deal of time initializing the record. To create more buttons, just call "Add Unit Button" more times. Note, however, that we only set up our record array, Unit Buttons, to hold 10 elements. If you need more than 10 buttons, you'd have to change that number as well.
The other advantage to the way we've done things is that you can reorganize buttons fairly easily. The buttons will be displayed in the order in which you initialize them. You can move buttons simply be reordering lines of code. It doesn't get much easier than that. It would be more difficult to reogranize the buttons because you'd have to change all the numbers around. Instead, you can just cut/paste code and buttons are instantly reorganized. Try it and you'll see how easy it is.
Creating the Dialog, Finally
We're nearly there. We've done a lot of work and so far we still can't see anything. Finally comes the part where all our hard work pays off.
Start by creating a new folder inside our Dialog folder and calling it "Unit Selection Dialog":
First, we're going to need one more constant. We need to know how many columns to display across. Technically, this could be a dialog constant, but it doesn't really matter. Just create an global integer constant called "nCols" and set it to 3 (it doesn't actually have to be 3, you can play around with it). You can put it in your Dialog Constants folder or in this folder, whichever makes more sense to you. I'll be keeping it in the Unit Selection Dialog folder.
Calculating Dialog Variables
Now create a new action definition inside of that folder, and call it "Unit Selection Dialog - Create". Before we get too carried away actually drawing the dialog, we're going to need to calculate a decent number of variables. Typically I do all my calculations inside the local variables section of the function, as I will do here. I find this breaks up the code nicely, but it doesn't always work if you need conditions or loops. For this dialog it will work fine.
The first thing we need to know is, given the number of buttons and the number of columns, how many rows will there be? On paper the calculation is pretty easy, but the galaxy editor makes it a little harder than it needs to be. Make a local variable inside your action definition called "nRows", and set it up as follows:
This is the most complicated mathematical calculation we need to perform, and I've had a few questions about it, so I'd like to go over it briefly. The short answer is we need to divide the number of buttons by the number of columns, but the long answer is a bit more complicated than that. nUnit Buttons and nCols are both integers. In programming, the end result of a division between two integers is always an integer. That is, the decimal part of the result is ignored. Basically it will always round down. This is not what we want. This is actually fine for our calculation (6 / 3) because it equals exactly 2, but if we had 7 buttons instead, we would still get 2 as a result, and 2 rows is not enough for 7 buttons. We actually need to always round up so that any remainder of the division creates a whole new row. To do this, we first need to convert or integers to reals so that we can actually get a decimal result. Then we use the function "Ceiling". The purpose of the ceiling function is to always round up any decimal value. It has a complimentary function, "Floor", which always rounds down (we won't be using that). Both of these functions are quite similar to the "Round" function, which you probably already understand.
Now that we know how many rows there we be, we can determine how big the dialog will be. Consider that the width of the dialog will equal: cDialog Extra + (cButton Size * nCols) + (cButton Gap * (nCols - 1)). This might not be immediately obvious, so let's go over it real quick. cDialog Extra is equal to cDialog Border times 2. So that's our total dialog border. Then, we need nCols worth of buttons. Finally, we need gaps between those buttons, but we don't need a gap after the last button. This is why we need to subtract 1 from nCols before we multiply times cButton Gap. It can be difficult to set something up like this in Galaxy Editor, so let's look at how it's laid out:
Similarly, the height of the dialog will equal: cDialog Extra + (cButton Size * nRows) + (cButton Gap * nRows - 1):
Once you've created Dialog Width, you can copy it and rename the copy to Dialog Height, then just change nCols to nRows.
For the buttons, we're going to use some variables (x and y), and just increment them every time we make a new button. That way, whenever we go to create a new button, it will be drawn at (x, y). So we will need to create these variables, and initialize them to cDialog Border.
There's a few other variables we'll need, but since we don't need to initialize them, we'll create them when we need them.
Drawing the Dialog, the Easy Part
If you've ever found drawing dialogs to be difficult, it's probably because you didn't perform all the setup we just did. If you don't, when you go to actually draw the dialog, suddenly you're scrambling to try and figure out how big it will be, where it will be, et cetera. All that stuff we already know. It's too much to try and figure out all that at once, I find it's a lot easier when you break it up into two pieces. After all the setup we just did, I think you'll find that actually drawing the dialog is really, really easy. When the editor asks you how large the dialog is going to be, you have the answer ready. I think you'll find that the code is also much easier to read as a result.
So let's draw the dialog:
It's really that easy, and the beauty is that there's no complex calculations in this code. All those calculations are performed above.
The next part is a little more complex, but if you've done a decent number of loops it's actually quite simple. We do, however, need a loop inside of a loop (one for rows, one for columns), so conceptually it's a little complicated. The skeleton of the loop will look like this:
Note that I don't ever use the "pick each" functions. I like to have a variable for everything. Also, you can't put a pick each integer inside of another one, so we'd have to use at least one "for each" here no matter what. I find it's just plain good practice to use "for each" all the time, as I have done here. This means I needed to create two new variables, iRow and iCol.
We'll start adding code to the loop by thinking about the outer loop first. At the beginning of each row, we want to set x = cDialog Border. At the end of each row, we want to add cButton Size + cButton Gap to y. This is what will cause us to move down to the next row each time.
Inside the inner loop, we're going to draw the actual button and icon. But we need to know which button and icon to draw, and iRow and iCol don't exactly tell us. We could calculate (iRow * nCols) + iCol every time, or we could have a variable start with zero and increment it by one after every loop. I've chosen to do the latter. Either way we'd need a new variable, which I called "iButton", and the initial value should be zero.
Drawing the Button
If you've worked with dialogs in the past, you may be aware that buttons can have text but not images. You can however have a button with an image simply by drawing the image on top of the button. One of the things I've learned from experience is that you want to put the tooltip on the button, and not the image.
So we will draw out dialog button in two steps. Step 1:
This is the simpler of the two steps where we create the button using our pre-calculated values. Notice that we set the tooltip here, and that the button text is "" (blank text).
Step 2:
In this step we actually need to add cButton Inlay to our x and y values. If we needed to use this calculation more than once (or if it were much more complicated), we would have pre-calculated it. However, it is fine to do small calculations like these inline.
It's very important to note that tiled is set to false. For most images, this is what you want (and it's not the default). It's no documented very well, but your options are "tiled" or "scaled". To get the image to scale, you set tiled to false. This is a common pitfall new dialog designers run into. We want our icon to scale.
Putting it All Together
A couple more steps need to happen in our inner loop. Our loop will work fine the way we have it, but if we decided to have 7 buttons instead of 6, it would still try to draw those last two buttons on the third row (buttons 8 and 9). To prevent that, we're going to add a simple if/then/else:
In the "Then" section is where we'll actually put the code we just created to draw our buttons. Nothing will go inside of the "Else" section.
After each button is displayed, we need to increase x by cButton Size + cButton Gap. Also, we will need to increment iButton by 1:
This code will not go inside of the if statement we just created. It will go just below it.
Finally, at the end of our function, we need to actually show the dialog. As the last step of our action definition (outside of any loops), add the following line:
When you put it all together, your action definition should look like this:
Testing Basic Functionality
We're finally ready to test our map and actually see something! ... well, almost. You'll just need to add two lines of code to our melee initialization trigger. First, you'd need to actually call the "Initialize Unit Buttons" action, then we need to call the "Unit Selection Dialog - Create" action.
Feel free to test your map at this point. It should look a lot like the demonstration image at the top of this post. However, you may notice right away that clicking a button doesn't actually do anything. We'll have to program that part shortly.
Before that, I encourage you to mess with the dialog constants to see what effect they have on the dialog. For example, by changing cDialog Border to 24 and cButton Gap to 0, I was able to pull the dialog items in much tighter to each other:
The numbers I arrived at were not by accident. They were arrived at by trying a number of different things first until I found a set up that looked good. Using constants allowed me to try new values quickly and easily.
Interaction
For this demonstration, we're going to keep the user interaction fairly simple. All users will see the same dialog. When someone selects a unit, that unit will be disabled for all other users. This will be fairly easy to do since they all see the same dialog. We just need to hide the dialog for the users who have already selected, and disable the button that they used.
Dialog Variables
Before we can do any of that, we actually need to back up a little bit. We need to save a couple of variables that we didn't save before when we were creating our dialog. First, we need to save the dialog itself as a variable. You can't use "Last Created Dialog" forever, eventually you need to save it to a variable for later retreival. In our case, in order to hide the dialog for our players, we'll need the dialog as a variable.
We'll also need to save the buttons. When we build our dialog callback, we'll have the function "Used Dialog Item" available to us, but we'll need to compare that value with our actual dialog buttons to see which ones were clicked. To do that, we'll need to save them off somewhere. We will use an array.
For pretty much any dialog you create, you will need to save the dialog and any buttons. You will also need to save any other elements that you might want to go back and change later. If you had a label that displayed HP, for example, you might need to save that label so you can go back and change its contents later. In our case, when we disable the button, we're going to need to disable the icon as well (otherwise it doesn't look right). So we're going to need to save the images as well.
In future tutorials, we'll use records to store all this, but for now we can just use normal variables. We will need a single dialog variable (we'll call it "User Selection Dialog"), and two arrays of dialog items (we'll call them "User Selection Buttons", and "User Selection Icons"). Since we made our User Buttons array size 10, we should make these size 10 as well. If we add more buttons, we will need to resize these arrays as well. (Unfortuantely, you can't use constants for array sizes). Create these variables inside your User Selection Dialog folder.
Then, add a few quick "set variable" lines to the User Selection Dialog - Creation action:
We've now successfully saved all the dialog variables we will need later.
Dialog Callback
For the last piece of the puzzle, create a new trigger inside the User Selection Dialog folder and call it "User Selection Dialog - Callback". For the event, use "Dialog Item is Used".
In this trigger, we're going to loop through all the dialog buttons to see if "Used Dialog Item" is one of them. If it is, we'll perform a few quick actions:
This trigger is actually quite simple. Hopefully the for each loop and the if'then/else are fairly familiar to you. The only thing we really need to analyze is what happens when we do find a button.
The first thing we do is hide the dialog for triggering player only (the player that selected something). Next, we disable the button and the icon for all players. Finally, we spawn a unit. For simplicity, I chose the center of the map as the spawn location.
Conclussion
It was a long road (and a long tutorial), but we finally got there. The dialog we built was fairly simple, but firm groundwork has been laid for all kinds of other great dialogs (which we will build in later tutorials). We've learned that constants allow us to easily tweak the properties of our dialog, which makes it easier to build great looking dialogs. We've learned the basics of using records to store similar information in one place, especially arrays of similar data.
Hopefully this tutorial was not too long, and hopefully I avoided a "wall of text" feel by breaking it up with pictures and examples. I attempted to build this tutorial from the viewpoint of someone who has never created a dialog (but has some experience with the editor), so if I went over something too quickly, please let me know and I will go back over it. I also welcome any suggestions about how I may improve future tutorials.
Pocket Warriors - A pokemon-style game with SC2 units and full banking. New demo coming soon!