User Types are an incredibly powerful new feature added during the 1.5 patch. They allow you to essentially create your own data type within the editor, and reference it's entries through triggers. That may not sound like much, but it's actually very useful. This tutorial is intended to teach you the basics of user types, so you won't need any prior experience.
If you're interested in seeing a "lighter" version of this tutorial, or you just hate me personally, FuzzYD has a great tutorial that teaches similar material here.
What's the point of a User Type?
As I said earlier, User Types basically allow you to create your own data types. Any Data type is really just a large xml file, and user types are no different. The beauty is that you can reference these user types within triggers, allowing you to do some really neat stuff. Some of you may have been following the Starmon project that I'm a part of (if you haven't been you should, because it's amazing). As you may have guessed by the name, it's a Pokemon-inspired map. We rely on user types heavily to run the game, and it would impossible to create otherwise. So let's get started :)
Creating our first user type
Think of a user type sort of like a big data table. The columns (which are called fields) are different "types" of data, and the rows (which are called instances) are "entries" in that data. Let's say you want to make a tower defense map- we can set up the waves through a user-type. For each wave, we'll want a name for the wave, a cash reward, and a list of enemies to spawn. We're going to make a user type with the following information inside:
Marine, Marine, Zergling
Zergling, Zergling, Zergling, Zergling, Zergling
Super Cool Wave
Zergling, Marine, Zergling, Baneling, Baneling
So, fire up the data editor (F7) and head to the user-types tab. Unlike most other data fields, this one's actually empty by default, because the standard dependencies don't use them. Right click and choose "Add User Type". Set the name to "Wave List", press suggest, and hit okay. Now we've got our empty user type, and it's time to start adding some content.
The first thing we need to do is set up the 'columns' (Wave Name, Wave Reward, and Enemy List), which are referred to as fields. To do this, open up the "fields+" field, and you'll see our empty table. Press the "Add" button to create our first column. Set the name to "Wave Name", the type to string, leave the value count at 1, and set the editor column to 1.
Before we continue, I'll describe what each of the important fields actually does:
The ID is the name of the column. It's for your benefit only, so you can keep track of what goes where
The Type is what type of information you're going to be entering in this field. It's just like with variables- you have to pick a type that you want to use.
The Value count is how many values you want to fit in this field. Some fields, like our name, will only need 1 value. But other fields, like 'Enemy List" will need many different values.
The editor column controls which order the columns appear in. Again, this is only for your benefit and ease-of-use, it does not actually affect anything.
The 'Modifiable' check box determines whether of not you can change these values after the game has started. For our purposes we're leaving it unchecked.
Go ahead and add the next two columns. Our reward column, which will be an 'integer' type with 1 value, and our enemy list, which will have 100 values and be the 'unit' type. Here's what it should look like:
Now it's time to actually add some waves. So press ok, and open the "Instances+" field. There's one field in here already, called "Default". This is where you can set up the default values for rows you'll add. We don't want to do that right now, but it's definitely useful. Go ahead and add a new entry. At the top of this new window you'll see an 'ID' field. Again, is for your benefit of organization- It doesn't really do anything on it's own. On the left, you'll see each of the "columns" we created before. Click on each one, and then set the value. For the name and reward entries, we'll only see one entry box on the right, but for our wave list, we'll see 100 values. This is because we set that column to use 100 values. Go ahead and set the first few values to whatever you feel like, then press ok.
From there, you can enter as many waves as you want. I just put 3 waves in. It's not much of a tower defense but it'll do for now. Don't panic when you look at your table- Even though you have 100 values for the wave units, you'll only see the first one listed. Here's what my table looks like:
Onto The Triggers!
We've created our data table, and it definitely looks awesome, but it's not doing us any good just sitting there; it would be like if we created a new weapon and then didn't give it to any units. We need to be able to reference it though triggers. If we were setting up a "real" tower defense, we'd probably want to use a more complex setup for the triggers, but the triggering isn't really the point of this tutorial.
First things first, create a new action definition called "Run Waves". If you're unfamiliar with Action Definitions, I'd encourage you to read up on them here, but you can use a trigger instead if you'd like. From there, create a new local integer variable called 'Wave#", and we're ready to proceed.
Add a "For each int" loop, setting the variable to "Wave#" and the starting value to 1. But how do we know how many waves there are total? Sure, we could change the max value every time we add a new wave, but that's extra work for us. Instead, we'll just use the "Number of User Data Instances" function and choose our wave list for it's single paramater. This handy function returns the number of instances in our user type (the number of rows, which in our case, is the number of waves). So however many waves we end up using, this loop will go through all of them. Neat, huh?
Now it's time to fill up the loop. For each wave, we want to show the wave names we recorded earlier. I'm just going to show it through a text message but you can do it any way you want. Use the "Text Message" action, and then open up the message text parameter. Here's where the user types come in- instead of typing in some text, go to the functions window and pick "convert string to text". This is because user types only hold strings (not text).
By default the string value will be "entered chat string", but we don't want that. Change the function to "User Data (String)". There's a different user data function for each type of value (int, real, string, unit, etc....), so you need to choose the one that matches the type you're using.
This function takes 3 parameters:
User Type: The User Type table we want to look up. For our purposes, it's the Waves table.
Instance: The specific instance we want to look up. In our case, it's the number of the wave we're on. We can't just plug in a number here, but we'll get to that later.
Field: Which field (or column) of the instance we want. In our case, we're looking for the wave title. Later we might want the unit type or reward.
Index: Which index of the field we want. This is going to be set to 1 for anything that only has 1 field (like wave name), but if we're using a multi-value field like we are for units, we need to specify which of the 100 unit values we're looking for.
For the user type field, you can simply select our Waves user type. The instance field is a little bit trickier- we could just pick an instance from the dropdown list, but the whole point is that this system is automatic and we shouldn't have to specify each wave. So instead, head over to the functions tab and pick "User Data Instance". This function returns the instance at a particular index in a user type- how convenient! It takes two paramaters- The User Type (which should be the same type as earlier, our waves list), and the index (which you should set to the wave# variable). So now, whatever wave we're currently looping through is the instance in the data table that's going to be opened.
Now the hard part's out of the way- set the field value to "Wave Name", since that's what we want, and set the index to 1, since we only have 1 value for the name anyways. (It's important to note that these user-type indexes actually start at 1, rather than 0 like most other indexes).
Here's the first part of our function if you want to check yours (click on the picture for a bigger version). If you were to call this action right now, you would see the three wave names output into the subtitle area.
Now we need to spawn the units- those are sort of important for any tower defense.
To do that, we're going to need one more local variable- call it x, and make it an integer.
Create another for loop inside the current one, and set it to loop from 1 to 100 using x.
Next, Add an if/then/else inside of this new loop. Add a condition to the loop.
We need to check each of these 100 values to see if there's actually any unit listed, or if we should stop spawning things. Use the "User Data (Unit)" function and set it up just like before. In this case, though, we want the index to be set to x (so we can check each of the 100 values instead of just the first), and we want to look up the enemy list instead of the wave names. Set the comparison to not equal (!=) and the right side to "None". So this condition will only return true if you've set the value to something, and it will return false if you haven't added a unit yet.
So, in the "Then" section, we need to actually create our unit. It's similar to how we showed the message earlier- we use the user data function to lookup a value we want.
Add a "Create Units with Default Facing" action. Set the point to wherever you feel like, and the player to 15. Leave the counter at 1.
Now we need to set the unit paramater. Instead of picking a unit, head over to the functions tab, and pick "User Data (Unit)". This is the same thing we did for the condition earlier, so hopefully you're starting to get the hang of it.
Add an order for the unit to move to your 'exit point' of the tower defense, and then add a short wait (1.5 seconds or so).
In the "Else" section, we know that we've reached the end of our spawn table. So add a "break" action. This terminates our loop early (before #100) but does not terminate the "parent" loop (the wave loop).
Now we need to add a wait to see if everything's dead yet. Add a "wait for conditions" actions and set the condition to "Number of units for player 15 = 0".
The only thing left is to reward the player with some money and extra time at the end of each wave. This should be pretty easy to do now that you've gotten the hang of reading user types. Just use the "modify player property" to add minerals, and then use the "user data (integer)" function to check how much to give the player. You can also add a 10 second wait to give the player some time at the end of the wave.
That's it, you're done! You can run the map right now and your tower defense spawner will work. You will need to add some towers to kill the enemy units, or at least add a region to kill them at the end (otherwise the next wave wont start). Hopefully, though, you can see why user types are so powerful. This was a pretty simple example, but it already made your work way easier. With the system set up, you can add, modify, review, or remove waves quickly and easily at any time. You don't need to tweak any variables, fix any triggers, or change any timers- it's all done automatically.
I mentioned a project by the name of Starmon earlier. I'm working as the project's main triggerer (along with TheAlmaity and Mozared, who do all the real work). This project is a great example of the power of user types- it's essentially pokemon in starcraft 2. Could you imagine how hard it would be to set up each and every pokemon, enemy trainer, and move using just variables? There probably wouldn't even be enough memory to hold it all! So even though the example we made today was pretty simple, user types are the key to some very awesome stuff. Maybe you want to create a Diablo-style RPG with randomized loot affixes, or a shoot-em-up with different types of weapons and ships, or a DOTA map where your hero information is stored through user types- the possibilities are endless.
Hopefully this tutorial has shown you the basics of user types. If not, feel completely free to send me a PM with any comments, questions, tutorial requests, or pieces of advice you've got. And as always, the same map is attached at the bottom of this post, so you can download it and take a look if your having any issues.
This information is too underestimated. I plan to use user types in my map. You can save and load user instances in bank file with just one action in triggers. I was shocked when figured it out. More people should start use it.