After having written our first mutator, and created a mutator with a user configuration menu, we'll now take a closer look at the UnrealScript language.
Posted by ambershee on Dec 3rd, 2007
Basic Server Side Coding.
We've written a mutator, we've made a configuration screen in UnrealEd and tied it up in code. This is all fantastic, we're on our way to fast becoming Unreal programming gurus! But there's one thing we'll quite definitely need to know - we need to get to grips with the unrealscript itself, otherwise we'll never get anywhere.
Whilst todays tutorial won't actually get us building anything, it is perhaps the most useful and important, because today we'll be focusing on exactly what unrealscript is, and how we can best deploy it.
Unrealscript is the soft architecture behind all of the core game mechanics in Unreal Tournament 3, and many, many other games. In Unreal Tournament 3, it not only controls things such as how the power ups and weapons are handled, it also deals with the gameplay fundamentals, and even the vast majority of the editor and effectively every ingame object, from a static mesh to a brush. We have access to all of that script, which makes us pretty powerful!
The concept behind unrealscript was to offer a powerfu, built in scripting language that could control various aspects of the unreal game, concepts of time, state, properties, and networking, all without the need to delve into over-complication C++ source code. Unrealscript thus focuses on these core game orientated elements and does so by taking out the need to deal with all those individual bits and pixels. It gives us a safe environment to experiment in, free of pointers, unsafe code and complexity, so that we can write games and not worry about dealing with the nastier elements of games programming.
The first thing we need to know is how to use, declare and manipulate the basicmost variables. These are the ones you'll come across and be using the most and are generally fundamentally the most important. Variables can be declared in local or global space. The difference? A global variable is accessible by all of the functions in that script class, and in fact by every other class in the game.Global variables must be declared before any functions are declared - otherwise the compiler will tell you that they are unexpected and will refuse to compile. Local variables are specific to individual functions, and can only be accessed by those functions - they are declared within the function itself, and manipulated from within the function.
There are also a number of different operators that can be used with our variables that allow us to modify and manipulate them as we need to. This is always done from within a function. Some operators can only be used to manipulate certain variables, and some may have different effects dependent on what variable they are attempting to manipulate.
What have we got here then? Well, we've declared three global variables and an array of variables - a byte, an integer, a floating point number and an array of integers. We could use these variables anywhere we wish - but we have to be mindful about what they have been set to in the past. The array is a special construction that in this instance will contain a sequence of 10 integer variables, and can often come in quite useful. We need to be careful with arrays however, and remember that the first number we put in the array is not position '1', but is in fact position '0'. This means that to access the first number in the array, we use FloatArray, and to access the last we use FloatArray. Confusing, perhaps, but this is how it works. We also gave some of our global variables default values. This is generally a good idea, because otherwise if we try to use them and haven't given them a value yet, then our script will return 'none' (which will appear in the script logs as a warning), and we could get some whacky things going on in the game, or something might not work at all.
We also have three local variables declared in our function; a boolean value (true or false), a string of characters and an unrealscript specific variable, a name. These variables can only be used inside this function, and just can't be accessed from anywhere else - in fact if you try, the unrealscript compiler will just give you a good telling off.
We didn't actually use our local variables in our DoMath function, which means that the unrealscript compiler will give us some warnings. It will tell us that they are unused, which means that we can happily delete them from our code, or comment them out. We can still run the game with the code, because warnings are just warnings, and not errors, afterall.
What we did do in our function, is set our floating point number to the sum of our integer and byte numbers. The difference between bytes, integers and floating point numbers is quite simple - their degree of accuracy. A byte is something that you generally will not use, as it is often better to just use an integer, just in case - but it can be used to store very small values. Integers are whole numbers and have a much broader range than bytes, which makes them safer to use in general. However, neither bytes not integers can be decimal numbers - which is where our floating point number comes in. Whilst the floating point can happily store whole and decimal numbers, it is important to remember that they are more costly in terms of processing and memory use, and whilst using the occassional floats is perfectly acceptable, it's better to use integer values when decimals aren't needed. Finally, we stored the floating point number in the float array, just because we might want it later.
There are a large number of different operators we could use - but we only used two in that particular example - the addition (+) and assignment(=) operators. There are many more operators that we can also use, for example our basic arithmatic;
A few simple operations. Notably, that last one might not be one you recognise right away - this is the modulo operation. It's use might not be immediately obvious, but as you become a better programmer, you will start to find cunning way to employ it and it's logic (for example, is a number odd or even?). Modulo is the special operator for finding the 'remainder'; it will divide the two numbers, and give you the remainder if there is any. In our case we divided a with b, there is no remainder, so it returns 0. Lastly wehave the increment and decrement operators, which are a more efficient (and lazy-programmer friendly) way of adding and subtracting one from a variable.
Lastly, with these simple operators, we can often combine them with the assignment operator to save ourselves a little bit of memory and performance, as well as writing out lots of unecessary code, and as we're programmers, we want to avoid unecessary code out of laziness.
In terms of logic, both of our subtractions and both of our multiplications do the same thing, but rather niftily, we can actually just assign the value immediately instead of using the same variable twice
There are also a number of other operators, whose uses are more complex and out of the scope of this article, and there are also a number of operators which are used in logic and flow control, which we will look at next.
It's all swell being able to add and subtract our numbers and create strings, but sooner rather than later we're going to find that more than just a bit limiting. We need to use logic or flow control in our programming. We need to check if certain requirements are met, we need to be able to cycle through certain things instead of going through and setting them over and over again. We need the almighty if statements, loops and cases.
That's a lot of code! But fortunately for us it's all very simple if we break it down and look at it one part at a time. The first thing we did in our function is take a look at our boolean expression, IsItTrue. We used the if statement to see if it was indeed equal to true. In the case of this code, it should always be that way, and we will enter the section of code directly underneath the if statement. However, if it was not true, we would have instead checked to see if it was not true, and gone into the else if statement to see if it was in fact false. If it had been false, the code block for the else if statement would have been executed instead. If something had gone very wrong, and our boolean was neither true or false (it's possible! we could have forgotten to give it a default value, then it would be none) then we would have gone into the codeblock in the final else statement.
If our boolean expression had a value (true or false), then we would have gone into our next flow control type - the for loop. For loops are sometimes quite useful, especially when working with arrays. Today, we used them a bit naughtily, and just used them to add some numbers, which would have been a lot easier had we just assigned the values to our variable Count manually - but then we wouldn't have learnt anything!
The for loop is relatively easy to implement. In our case, we created an integer value called i, purely for use in the loop, and set it to 0. We then told the loop that we want it to stop when our integer i is no longer smaller than Loops (we could have checked for any number of conditions!), and every time the loop goes around, we add 1 to our integer i. This means we are counting the number of times the loop goes around in i, and checking to see if it has become the same size as or larger than Loops - then we add either 1 or 2 to Count as necessary, giving as an end result of 3 or 6 (because we loop three times!).
We finally used a case statement to check what to put in our string. We only checked for the possible values
We could have checked for other things, for example if it was exactly equal to Loops like in our if statements (using the == logic operator) . We could in fact use any of these operators;
Think you can handle all those? I certainly think so. So let's move onto something else and take a look at a couple of more advanced variables.
You've already seen arrays, but you might not know about these wonderful other tools we have at our disposal - the struct and the enumerator (enum). Just like an array, both the struct and enumerator are fairly common in unrealscript, so you'll see them a lot and it helps to know what they are.
Structs are handy tools that can be used to keep a lot of related data in one organised space. That data can then be more easily duplicated when required. A good example could be a struct made to contain information about a monster
Ok, so this is how we declare and give default values to a struct. We must also declare structs at the same time as we declare our variables. If we create a struct, then we need to do so after we declare it, and we need to do so before we start writing functions. Finicky? Not really, but it's certainly logical. Structs can also be made local, although this is generally not necessary, so you won't see it used very often - but it can be done; have you ever considered colours? The LinearColor data type is in fact a struct, containing four integer numbers, for the Red, Blue, Green and Alpha qualities of the colour. Very clever.
Enumerators are a little more complex, but still essentially very easy to create and use. Essentially they are declared and created in the same manner as a struct, although an enumerator will never have default values. Enumerators go hand in hand with switch statements and if statements.
That wasn't so horrible, was it? Well there's one last thing we can do, but we won't go over it in much too much detail, but you'll know when you want to use it. Because unrealscript is a heavily object orientated scripting language, we can also create and use objects (just like variable, structs and enumerators) of a particular class, so long as it isn't abstract.
Confusing? Perhaps. But what you can see here is how creating an object of our class type is a way that we can create the necessary subclasses for certain objects and manipulate them from a parent class, without the need to do any nasty 'tight coupling' and overcomplicated code work.
We've already done things like creating functions and new classes, but let's take a step back and look at how we're supposed to do it. Notably, every single unrealscript file (.uc) only ever contains one class, no more, no less. It's impossible to do otherwise, because the compiler will give you a slap on the wrist and tell you that you're doing something unexpected. A class however may contain any number of functions, which are declared like so;
These two functions are quite similar, but also quite different. We declare a function with the function keyword. Our first function takes an integer - it must be passed one when you call it. It is called by the second function and is passed the cunningly named integer Integer. The second function returns an integer. It is thus expected to give a value when it has finished. You can actually return the integer at any point in your code, but it will then end the function and complete no more of the logic you have written into it.
You may notice some other forms of functions, such as afunction with a specifier. These specifiers are used to denote specific properties of that function, for example one with the native specifier maps directly onto C++ source code; therefore it is probably one that as a budding unrealscript programmer, you'll be wanting to avoid. One you might want to use is the simulated specifier. This particular specifier says that this function is one that can be simulated on the client machine instead of on the server. Great for saving bandwidth, but be careful when you use it!
This concludes our brief introduction into the basics of the unrealscript language. Hoepfully, you'll know enough of the ropes to really get out and start writing some real code. This is by no means as far as unrealscript goes however, we have barely touched the surface!
To learn more about the specifics of unrealscript, you can download a reference from the Unreal Developer Network and a cheat sheet here. Happy programming!