Check out our discord at https://discord.gg/3u69jMa
UnrealScript Language Reference: Difference between revisions
No edit summary |
No edit summary |
||
Line 329: | Line 329: | ||
In Unreal, classes are objects just like actors, textures, and sounds are objects. Class objects belong to the class named "class". Now, there will often be cases where you'll want to store a reference to a class object, so that you can spawn an actor belonging to that class (without knowing what the class is at compile-time). For example: | In Unreal, classes are objects just like actors, textures, and sounds are objects. Class objects belong to the class named "class". Now, there will often be cases where you'll want to store a reference to a class object, so that you can spawn an actor belonging to that class (without knowing what the class is at compile-time). For example: | ||
<syntaxhighlight lang="c++"> | |||
var() class C; | |||
var actor A; | |||
A = Spawn( C ); // Spawn an actor belonging to some arbitrary class C. | |||
</syntaxhighlight> | |||
Now, be sure not to confuse the roles of a class C, and an object O belonging to class C. To give a really shaky analogy, a class is like a pepper grinder, and an object is like pepper. You can use the pepper grinder (the class) to create pepper (objects of that class) by turning the crank (calling the Spawn function)...BUT, a pepper grinder (a class) is not pepper (an object belonging to the class), so you MUST NOT TRY TO EAT IT! | |||
When declaring variables that reference class objects, you can optionally use the special class<classlimitor> syntax to limit the variable to only containing references to classes which expand a given superclass. For example, in the declaration: | |||
<syntaxhighlight lang="c++"> | |||
var class<actor> ActorClass; | |||
</syntaxhighlight> | |||
The variable ActorClass may only reference a class that expands the "actor" class. This is useful for improving compile-time type checking. For example, the Spawn function takes a class as a parameter, but only makes sense when the given class is a subclass of Actor, and the class<classlimitor> syntax causes the compiler to enforce that requirement. | |||
As with dynamic object casting, you can dynamically cast classes like this: | |||
<syntaxhighlight lang="c++"> | |||
class<actor>( SomeFunctionCall() ) | |||
</syntaxhighlight> | |||
'''Enumerations''' | |||
Enumerations exist in UnrealScript as a convenient way to declare variables that can contain "one of" a bunch of keywords. For example, the actor class contains the enumeration EPhysics which describes the physics which Unreal should apply to the actor. This can be set to one of the predefined values like PHYS_None, PHYS_Walking, PHYS_Falling, and so on. | |||
Internally, enumerations are stored as byte variables. In designing UnrealScript, enumerations were not seen as a necessity, but it makes code so much easier to read to see that an actor’s physics mode is being set to "PHYS_Swimming" than (for example) "3". | |||
Here is sample code that declares enumerations. | |||
<syntaxhighlight lang="c++"> | |||
// Declare the EColor enumeration, with three values. | |||
enum EColor | |||
{ | |||
CO_Red, | |||
CO_Green, | |||
CO_Blue | |||
}; | |||
// Now, declare two variables of type EColor. | |||
var EColor ShirtColor, HatColor; | |||
// Alternatively, you can declare variables and | |||
// enumerations together like this: | |||
var enum EFruit | |||
{ | |||
FRUIT_Apple, | |||
FRUIT_Orange, | |||
FRUIT_Bannana | |||
} FirstFruit, SecondFruit; | |||
</syntaxhighlight> | |||
In the Unreal source, we always declare enumeration values like LT_Steady, PHYS_Falling, and so on, rather than as simply "Steady" or "Falling". This is just a matter of programming style, and is not a requirement of the language. | |||
UnrealScript only recognizes unqualified enum tags (like FRUIT_Apple) in classes where the enumeration was defined, and in its subclasses. If you need to refer to an enumeration tag defined somewhere else in the class hierarchy, you must "qualify it": | |||
<syntaxhighlight lang="c++"> | |||
FRUIT_Apple // If Unreal can't find this enum tag... | |||
EFruit.FRUIT_Apple // Then qualify it like this. | |||
</syntaxhighlight> | |||
'''Structs''' | |||
An UnrealScript struct is a way of cramming a bunch of variables together into a new kind of super-variable called a struct. UnrealScript structs are just like C structs, in that they can contain any simple variables or arrays. | |||
You can declare a struct as follows: | |||
<syntaxhighlight lang="c++"> | |||
// A point or direction vector in 3D space. | |||
struct Vector | |||
{ | |||
var float X; | |||
var float Y; | |||
var float Z | |||
}; | |||
</syntaxhighlight> | |||
Once you declare a struct, you are ready to start declaring specific variables of that struct type: | |||
<syntaxhighlight lang="c++"> | |||
// Declare a bunch of variables of type Vector. | |||
var Vector Position; | |||
var Vector Destination; | |||
</syntaxhighlight> | |||
To access a component of a struct, use code like the following. | |||
<syntaxhighlight lang="c++"> | |||
function MyFunction() | |||
{ | |||
Local Vector A, B, C; | |||
// Add some vectors. | |||
C = A + B; | |||
// Add just the x components of the vectors. | |||
C.X = A.X + B.X; | |||
// Pass vector C to a function. | |||
SomeFunction( C ); | |||
// Pass certain vector components to a function. | |||
OtherFunction( A.X, C.Z ); | |||
} | |||
</syntaxhighlight> | |||
You can do anything with Struct variables that you can do with other variables: you can assign variables to them, you can pass them to functions, and you can access their components. | |||
There are several Structs defined in the Object class which are used throughout Unreal. You should become familiar with their operation, as they are fundamental building blocks of scripts: | |||
* Vector: A unique 3D point or vector in space, with an X, Y, and Z component. | |||
* Plane: Defines a unique plane in 3D space. A plane is defined by its X, Y, and Z components (which are assumed to be normalized) plus its W component, which represents the distance of the plane from the origin, along the plane’s normal (which is the shortest line from the plane to the origin). | |||
* Rotation: A rotation defining a unique orthogonal coordinate system. A rotation contains Pitch, Yaw, and Roll components. | |||
* Coords: An arbitrary coordinate system in 3D space. | |||
* Color: An RGB color value. | |||
* Region: Defines a unique convex region within a level. | |||
'''Functions''' | |||
In UnrealScript, you can declare new functions and write new versions of existing functions. Functions can take one or more parameters (of any variable type UnrealScript supports), and can optionally return a value. Though most functions are written directly in UnrealScript, you can also declare functions that can be called from UnrealScript, but which are implemented in C++ and reside in a DLL. The Unreal technology supports all possible combinations of function calling: The C++ engine can call script functions; script can call C++ functions; and script can call script. | |||
Here is a simple function declaration. This function takes a vector as a parameter, and returns a floating point number: | |||
<syntaxhighlight lang="c++"> | |||
// Function to compute the size of a vector. | |||
function float VectorSize( vector V ) | |||
{ | |||
return sqrt( V.X * V.X + V.Y * V.Y + V.Z * V.Z ); | |||
} | |||
</syntaxhighlight> | |||
The word "function" always precedes a function declaration. It is followed by the optional return type of the function (in this case, "float"), then the function name, and then the list of function parameters enclosed in parenthesis. | |||
When a function is called, the code within the brackets is executed. Inside the function, you can declare local variables (using the "local" keyword"), and execute any UnrealScript code. The optional "return" keyword causes the function to immediately return a value. | |||
You can pass any UnrealScript types to a function (including arrays), and a function can return any type. | |||
By default, any local variables you declare in a function are initialized to zero. | |||
Function calls can be recursive. For example, the following function computes the factorial of a number: | |||
<syntaxhighlight lang="c++"> | |||
// Function to compute the factorial of a number. | |||
function int Factorial( int Number ) | |||
{ | |||
if( Number <= 0 ) | |||
return 1; | |||
else | |||
return Number * Factorial( Number – 1 ); | |||
} | |||
</syntaxhighlight> | |||
Some UnrealScript functions are called by the engine whenever certain events occur. For example, when an actor is touched by another actor, the engine calls its "Touch" function to tell it who is touching it. By writing a custom "Touch" function, you can take special actions as a result of the touch occuring: | |||
<syntaxhighlight lang="c++"> | |||
// Called when something touches this actor. | |||
function Touch( actor Other ) | |||
{ | |||
Log( "I was touched!") | |||
Other.Message( "You touched me!" ); | |||
} | |||
</syntaxhighlight> | |||
The above function illustrates several things. First of all, the function writes a message to the log file using the "Log" command (which is the equivalent of Basic’s "print" command and C’s "printf"). Second, it calls the "Message" function residing in the actor Other. Calling functions in other actors is a common action in UnrealScript, and in object-oriented languages like Java in general, because it provides a simple means for actors to communicate with each other. | |||
'''Function parameter specifiers''' | |||
When you normally call a function, UnrealScript makes a local copy of the parameters you pass the function. If the function modifies some of the parameters, those don’t have any effect on the variables you passed in. For example, the following program: | |||
<syntaxhighlight lang="c++"> | |||
function int DoSomething( int x ) | |||
{ | |||
x = x * 2; | |||
return x; | |||
} | |||
function int DoSomethingElse() | |||
{ | |||
local int a, b; | |||
a = 2; | |||
log( "The value of a is " $ a ); | |||
b = DoSomething( a ); | |||
log( "The value of a is " $ a ); | |||
log( "The value of b is " $ b ); | |||
} | |||
</syntaxhighlight> | |||
Produces the following output when DoSomethingElse is called: | |||
<pre> | |||
The value of a is 2 | |||
The value of a is 2 | |||
The value of b is 4 | |||
</pre> | |||
In other words, the function DoSomething was futzing with a local copy of the variable "a" which was passed to it, and it was not affecting the real variable "a". | |||
The "out" specified lets you tell a function that it should actually modify the variable that is passed to it, rather than making a local copy. This is useful, for example, if you have a function that needs to return several values to the caller. You can juse have the caller pass several variables to the function which are "out" values. For example: | |||
<syntaxhighlight lang="c++"> | |||
// Compute the minimum and maximum components of a vector. | |||
function VectorRange( vector V, out float Min, out float Max ) | |||
{ | |||
// Compute the minimum value. | |||
if ( V.X<V.Y && V.X<V.Z ) Min = V.X; | |||
else if( V.Y<V.Z ) Min = V.Y; | |||
else Min = V.Z; | |||
// Compute the maximum value. | |||
if ( V.X>V.Y && V.X>V.Z ) Max = V.X; | |||
else if( V.Y>V.Z ) Max = V.Y; | |||
else Max = V.Z; | |||
} | |||
</syntaxhighlight> | |||
Without the "out" keyword, it would be painful to try to write functions that had to return more than one value. | |||
With the "optional" keyword, you can make certain function parameters optional, as a convenience to the caller. For UnrealScript functions, optional parameters which the caller doesn’t specify are set to zero. For intrinsic functions, the default values of optional parameters depends on the function. For example, the Spawn function takes an optional location and rotation, which default to the spawning actor’s location and rotation. | |||
The "coerce" keyword forces the caller’s parameters to be converted to the specified type (even if UnrealScript normally would not perform the conversion automatically). This is useful for functions that deal with strings, so that the parameters are automatically converted to strings for you. | |||
'''Function overriding''' | |||
"Function overriding" refers to writing a new version of a function in a subclass. For example, say you’re writing a script for a new kind of monster called a Demon. The Demon class, which you just created, expands the Pawn class. Now, when a pawn sees a player for the first time, the pawn’s "SeePlayer" function is called, so that the pawn can start attacking the player. This is a nice concept, but say you wanted to handle "SeePlayer" differently in your new Demon class. How do you do this? Function overriding is the answer. | |||
To override a function, just cut and paste the function definition from the parent class into your new class. For example, for SeePlayer, you could add this to your Demon class. | |||
<syntaxhighlight lang="c++"> | |||
// New Demon class version of the Touch function. | |||
function SeePlayer( actor SeenPlayer ) | |||
{ | |||
log( "The demon saw a player" ); | |||
// Add new custom functionality here… | |||
} | |||
</syntaxhighlight> | |||
Function overriding is the key to creating new UnrealScript classes efficiently. You can create a new class that expands on an existing class. Then, all you need to do is override the functions which you want to be handled differently. This enables you to create new kinds of objects without writing gigantic amounts of code. | |||
Several functions in UnrealScript are declared as "final". The "final" keyword (which appears immediately before the word "function") says "this function cannot be overridden by child classes". This should be used in functions which you know nobody would want to override, because it results in faster script code. For example, say you have a "VectorSize" function that computes the size of a vector. There’s absolutely no reason anyone would ever override that, so declare it as "final". On the other hand, a function like "Touch" is very context-dependent and should not be final. | |||
'''Advanced function specifiers''' |
Revision as of 14:45, 20 May 2024
UnrealScript Language Reference
Tim Sweeney
Epic MegaGames, Inc.
Introduction
Purpose of this document
This is a technical document describing the UnrealScript language. It’s not a tutorial, nor does it provide detailed examples of useful UnrealScript code. For examples of UnrealScript prior to release of Unreal, the reader is referred to the source code to the Unreal scripts, which provides tens of thousands of lines of working UnrealScript code which solves many problems such as AI, movement, inventory, and triggers. A good way to get started is by printing out the "Actor", "Object", "Pawn", "Inventory", and "Weapon" scripts.
This document assumes that the reader has a working knowledge of C/C++, is familiar with object-oriented programming, has played Unreal and has used the UnrealEd editing environment.
Design goals of UnrealScript
UnrealScript was created to provide the development team and the third-party Unreal developers with a powerful, built-in programming language that maps naturally onto the needs and nuances of game programming.
The major design goals of UnrealScript are:
- To support the major concepts of time, state, properties, and networking which traditional programming languages don’t address. This greatly simplifies UnrealScript code. The major complication in C/C++ based AI and game logic programming lies in dealing with events that take a certain amount of game time to complete, and with events which are dependent on aspects of the object’s state. In C/C++, this results in spaghetti-code that is hard to write, comprehend, maintain, and debug. UnrealScript includes native support for time, state, and network replication which greatly simplify game programming.
- To provide Java-style programming simplicity, object-orientation, and compile-time error checking. Much as Java brings a clean programming platform to Web programmers, UnrealScript provides an equally clean, simple, and robust programming language to 3D gaming. The major programming concepts which UnrealScript derives from Java are: a pointerless environment with automatic garbage collection; a simple single-inheretance class graph; strong compile-time type checking; a safe client-side execution "sandbox"; and the familiar look and feel of C/C++/Java code.
- To enable rich, high level programming in terms of game objects and interactions rather than bits and pixels. Where design tradeoffs had to be made in UnrealScript, I sacrificed execution speed for development simplicity and power. After all, the low-level, performance-critical code in Unreal is written in C/C++ where the performance gain outweighs the added complexity. UnrealScript operates at a level above that, at the object and interaction level, rather than the bits and pixels level.
During the early development of UnrealScript, several major different programming paradigms were explored and discarded before arriving at the current incarnation. First, I researched using the Sun and Microsoft Java VM’s for Windows as the basis of Unreal’s scripting language. It turned out that Java offered no programming benefits over C/C++ in the Unreal context, added frustraging restrictions due to the lack of needed language features (such as operator overloading), and turned out to be unfathomably slow due to both the overhead of the VM task switch and the inefficiencies of the Java garbage collector in the case of a large object graph. Second, I based an early implementation of UnrealScript on a Visual Basic variant, which worked fine, but was less friendly to programmers accustomed to C/C++. The final decision to base UnrealScript on a C++/Java variant was based on the desire to map game-specific concepts onto the language definition itself, and the need for speed and familiarity. This turned out to be a good decision, as it has greatly simplified many aspects of the Unreal codebase.
Example program structure
This example illustrates a typical, simple UnrealScript class, and it highlights the syntax and features of UnrealScript. Note that this code may differ from that which appears in the current Unreal source, as this documentation is not synced with the code.
//=============================================================================
// TriggerLight.
// A lightsource which can be triggered on or off.
//=============================================================================
class TriggerLight expands Light
package(UnEngine);
//-----------------------------------------------------------------------------
// Variables.
var() float ChangeTime; // Time light takes to change from on to off.
var() bool bInitiallyOn; // Whether it's initially on.
var() bool bDelayFullOn; // Delay then go full-on.
var ELightType InitialType; // Initial type of light.
var float InitialBrightness; // Initial brightness.
var float Alpha, Direction;
var actor Trigger;
//-----------------------------------------------------------------------------
// Engine functions.
// Called at start of gameplay.
function BeginPlay()
{
// Remember initial light type and set new one.
Disable( 'Tick' );
InitialType = LightType;
InitialBrightness = LightBrightness;
if( bInitiallyOn )
{
Alpha = 1.0;
Direction = 1.0;
}
else
{
LightType = LT_None;
Alpha = 0.0;
Direction = -1.0;
}
}
// Called whenever time passes.
function Tick( float DeltaTime )
{
LightType = InitialType;
Alpha += Direction * DeltaTime / ChangeTime;
if( Alpha > 1.0 )
{
Alpha = 1.0;
Disable( 'Tick' );
if( Trigger != None )
Trigger.ResetTrigger();
}
else if( Alpha < 0.0 )
{
Alpha = 0.0;
Disable( 'Tick' );
LightType = LT_None;
if( Trigger != None )
Trigger.ResetTrigger();
}
if( !bDelayFullOn )
LightBrightness = Alpha * InitialBrightness;
else if( (Direction>0 && Alpha!=1) || Alpha==0 )
LightBrightness = 0;
else
LightBrightness = InitialBrightness;
}
//-----------------------------------------------------------------------------
// Public states.
// Trigger turns the light on.
state() TriggerTurnsOn
{
function Trigger( actor Other, pawn EventInstigator )
{
Trigger = None;
Direction = 1.0;
Enable( 'Tick' );
}
}
// Trigger turns the light off.
state() TriggerTurnsOff
{
function Trigger( actor Other, pawn EventInstigator )
{
Trigger = None;
Direction = -1.0;
Enable( 'Tick' );
}
}
// Trigger toggles the light.
state() TriggerToggle
{
function Trigger( actor Other, pawn EventInstigator )
{
log("Toggle");
Trigger = Other;
Direction *= -1;
Enable( 'Tick' );
}
}
// Trigger controls the light.
state() TriggerControl
{
function Trigger( actor Other, pawn EventInstigator )
{
Trigger = Other;
if( bInitiallyOn ) Direction = -1.0;
else Direction = 1.0;
Enable( 'Tick' );
}
function UnTrigger( actor Other, pawn EventInstigator )
{
Trigger = Other;
if( bInitiallyOn ) Direction = 1.0;
else Direction = -1.0;
Enable( 'Tick' );
}
}
The key elements to look at in this script are:
- The class declaration. Each class "expands" (derives from) one parent class, and each class belongs to a "package", a collection of objects that are distributed together. All functions and variables belong to a class, and are only accessible through an actor that belongs to that class. There are no system-wide global functions or variables.
- The variable declarations. UnrealScript supports a very diverse set of variable types including most base C/Java types, object references, structs, and arrays. In addition, variables can be made into editable properties which designers can access in UnrealEd without any programming.
- The functions. Functions can take a list of parameters, and they optionally return a value. Functions can have local variables. Some functions are called by the Unreal engine itself (such as BeginPlay), and some functions are called from other script code elsewhere (such as Trigger).
- The code. All of the standard C and Java keywords are supported, like "for", "while", "break", "switch", "if", and so on. Braces and semicolons are used in UnrealScript as in C, C++, and Java.
- Actor and object references. Here you see several cases where a function is called within another object, using an object reference.
- The "state" keyword. This script defines several "states", which are groupings of functions, variables, and code which are executed only when the actor is in that state.
- Note that all keywords, variable names, functions, and object names in UnrealScript are case-insensitive. To UnrealScript, "Demon", "demON", and "demon" are the same thing.
The Unreal Virtual Machine
The Unreal Virtual Machine consists of several components: The server, the client, the rendering engine, and the engine support code.
The Unreal server controls all gameplay and interaction between players and actors. In a single-player game, both the Unreal client and the Unreal server are run on the same machine; in an Internet game, there is a dedicated server running on one machine; all players connect to this machine and are clients.
All gameplay takes place inside a "level", a self-contained environment containing geometry and actors. Though UnrealServer may be capable of running more than one level simultaneously, each level operates independently, and are shielded from each other: actors cannot travel between levels, and actors on one level cannot communicate with actors on another level.
Each actor in a map can either be under player control (there can be many players in a network game) or under script control. When an actor is under script control, its script completely defines how the actor moves and interacts with other actors.
With all of those actors running around, scripts executing, and events occuring in the world, you're probably asking how one can understand the flow of execution in an UnrealScript. The answer is as follows:
To manage time, Unreal divides each second of gameplay into "Ticks". A tick is the smallest unit of time in which all actors in a level are updated. A tick typically takes between 1/100th to 1/10th of a second. The tick time is limited only by CPU power; the faster machine, the lower the tick duration is.
Some commands in UnrealScript take zero ticks to execute (i.e. they execute without any game-time passing), and others take many ticks. Functions which require game-time to pass are called "latent functions". Some examples of latent functions include "Sleep", "FinishAnim", and "MoveTo". Latent functions in UnrealScript may only be called from code within a state, not from code within a function.
While an actor is executing a latent function, that actor's state execution doesn't continue until the latent function completes. However, other actors, or the VM, may call functions within the actor. The net result is that all UnrealScript functions can be called at any time, even while latent functions are pending.
In traditional programming terms, UnrealScript acts as if each actor in a level has its own "thread" of execution. Internally, Unreal does not use Windows threads, because that would be very inefficient (Windows 95 and Windows NT do not handle thousands of simultaneous threads efficiently). Instead, UnrealScript simulates threads. This fact is transparent to UnrealScript code, but becomes very apparent when you write C++ code which interacts with UnrealScript.
All UnrealScripts execute in parallel. If there are 100 monsters walking around in a level, all 100 of those monsters' scripts are executing simultaneously and independently.
Class overview
Before beginning work with UnrealScript, it’s important to understand the high-level relationships of objects within Unreal. The architecture of Unreal is a major departure from that of most other games: Unreal is purely object-oriented (much like COM/ActiveX), in that it has a well-defined object model with support for high-level object oriented concepts such as the object graph, serialization, object lifetime, and polymorphism. Historically, most games have been designed monolithically, with their major functionality hardcoded and unexpandable at the object level, though many games, such as Doom and Quake, have proven to be very expandable at the content level. There is a major benefit to Unreal’s form of object-orientation: major new functionality and object types can be added to Unreal at runtime, and this expansion can take the form of subclassing, rather than (for example) by modifying a bunch of existing code. This form of extensibility is extremely powerful, as it encourages the Unreal community to create Unreal enhancements that all interoperate.
Object is the parent class of all objects in Unreal. All of the functions in the Object class are accessible everywhere, because everything derives from Object. Object is an abstract base class, in that it doesn’t do anything useful. All functionality is provided by subclasses, such as Texture (a texture map), TextBuffer (a chunk of text), and Class (which describes the class of other objects).
Actor (expands Object) is the parent class of all standalone game objects in Unreal. The Actor class contains all of the functionality needed for an actor to move around, interact with other actors, affect the environment, and do other useful game-related things.
Pawn (expands Actor) is the parent class of all creatures and players in Unreal which are capable of high-level AI and player controls.
Class (expands Object) is a special kind of object which describes a class of object. This may seem confusing at first: a class is an object, and a class describes certain objects. But, the concept is sound, and there are many cases where you will deal with Class objects. For example, when you spawn a new actor in UnrealScript, you can specify the new actor’s class with a Class object.
With UnrealScript, you can write code for any Object class, but 99% of the time, you will be writing code for a class derived from Actor. Most of the useful UnrealScript functionality is game-related and deals with actors.
Major elements of UnrealScript
The class declaration
Each script corresponds to exactly one class, and the script begins by declaring the class, the class’s parent, and any additional information that is relevent to the class. The simplest form is:
class MyClass expands MyParentClass
package(MyPackage);
Here I am declaring a new class named "MyClass", which inherets the functionality of "MyParentClass". Additionally, the class resides in the package named "MyPackage".
Each class inherets all of the variables, functions, and states from its parent class. It can then add new variable declarations, add new functions (or override the existing functions), add new states (or add functionality to the existing states).
The typical approach to class design in UnrealScript is to make a new class (for example a Minotaur monster) which expands an existing class that has most of the functionality you need (for example the Pawn class, the base class of all monsters). With this approach, you never need to reinvent the wheel – you can simply add the new functionality you want to customize, while keeping all of the existing functionality you don’t need to customize. This approach is especially powerful for implementing AI in Unreal, where the built-in AI system provides a tremendous amount of base functionality which you can use as building blocks for your custom creatures.
The class declaration can take several optional specifiers that affect the class:
- Intrinsic: Says "this class uses behind-the-scenes C++ support". Unreal expects Intrinsic classes to contain a C++ implementation in the DLL corresponding to the class’s package. For example, if your package is named "Robots", Unreal looks in the "Robots.dll" for the C++ implementation of the intrinsic class, which is generated by the C++ IMPLEMENT_CLASS macro.
- HideParent: Says "when a user is editing an object of this class in UnrealEd, don’t show any of the parent class’s properties in the editor".
- Abstract: Declares the class as an "abstract base class". This prevents the user from adding actors of this class to the world in UnrealEd, because the class isn’t meaningful on its own. For example, the "Pawn base class is abstract, while the "Brute" subclass is not abstract – you can place a Brute in the world, but you can’t place a Pawn in the world.
- Guid: Associates a globally unique identifier (a 128-bit number) with the class. This Guid is currently unused, but will be relevent when native COM support is later added to Unreal.
- Transient: Says "objects belonging to this class should never be saved on disk". Only useful in conjunction with certain kinds of intrinsic classes which are non-persistent by nature, such as players or windows.
- ScriptConst: Says "objects of this class must be declared Const in UnrealScript", to prevent them from being changed during gameplay.
Variables
Here are some examples of instance variable declarations in UnrealScript:
var int a; // Declare an integer variable named "A".
var byte Table[64]; // Declare an array of 64 bytes named "Table".
var string[32] PlayerName; // Declare a max 32-character string.
var actor Other; // Declare a variable referencing an actor.
Variables can appear in two kinds of places in UnrealScript: instance variables, which apply to an entire object, appear immediately after the class declarations. Local variables appear within a function, and are only active while that function executes. Instance variables are declared with the "var" keyword. Local variables are declard with the "local" keyword.
Here are the basic variable types supported in UnrealScript:
- byte: A single-byte value ranging from 0 to 255.
- int: A 32-bit integer value.
- bool: A boolean value: either "true" or "false".
- float: A 32-bit floating point number.
- string: A string of characters.
- name: The name of an item in Unreal (such as the name of a function, state, class, etc). Names are stored as a 16-bit index into the global name table. Names correspond to simple strings of 1-31 characters. Names are not like strings: strings can be modified dynamically, but names can only take on predefined name values.
- Enumeration: A variable that can take on one of several predefined name values. For example, the ELightType enumeration defined in the Actor script describes a dynamic light and takes on a value like LT_None, LT_Pulse, LT_Strobe, and so on.
- Object and actor references: A variable that refers to another object or actor in the world. For example, the Pawn class has an "Enemy" actor reference that specifies which actor the pawn should be trying to attack. Object and actor references are very powerful tools, because they enable you to access the variables and functions of another actor. For example, in the Pawn script, you can write "Enemy.Damage(123)" to call your enemy’s Damage function – resulting in the enemy taking damage. Object references may also contain a special value called "None", which is the equivalent of the C "NULL" pointer: it says "this variable doesn’t refer to any object".
- Structs: Similar to C structures, UnrealScript structs let you create new variable types that contain sub-variables. For example, two commonly-used structs are "vector", which consists of an X, Y, and Z component; and "rotator", which consists of a pitch, yaw, and roll component.
Variables may also contain additional specifiers such as "const" that further describe the variable. Actually, there are quite a lot of specifiers which you wouldn’t expect to see in a general-purpose programming language, mainly as a result of wanting UnrealScript to natively support many game- and environment- specific concepts:
- const: Advanced. Treats the contents of the variable as a constant. In UnrealScript, you can read the value of const variables, but you can’t write to them. "Const" is only used for variables which the engine is responsible for updating, and which can’t be safely updated from UnrealScript, such as an actor’s Location (which can only be set by calling the MoveActor function).
- input: Advanced. Makes the variable accessible to Unreal’s input system, so that input (such as button presses and joystick movements) can be directly mapped onto it. Only relevent with variables of type "byte" and "float".
- transient: Advanced. Declares that the variable is for temporary use, and isn’t part of the object’s persistent state. Transient variables are not saved to disk. Transient variables are initialized to zero when an actor is loaded.
- intrinsic: Advanced. Declares that the variable is loaded and saved by C++ code, rather than by UnrealScript.
- private: The variable is private, and may only be accessed by the class's script; no other classes (including subclasses) may access it.
Arrays are declared using the following syntax:
var int MyArray[20]; // Declares an array of 20 ints.
UnrealScript supports only single-dimensional arrays, though you can simulate multidimensional arrays by carrying out the row/column math yourself.
In UnrealScript, you can make an instance variable "editable", so that users can edit the variable’s value in UnrealEd. This mechanism is responsible for the entire contents of the "Actor Properties" dialog in UnrealEd: everything you see there is simply an UnrealScript variable, which has been declared editable.
The syntax for declaring an editable variable is as follows:
var() int MyInteger; // Declare an editable integer in the default category.
var(MyCategory) bool MyBool; // Declare an editable integer in "MyCategory".
Object and actor reference variables
You can declare a variable that refers to an actor or object like this:
var actor A; // An actor reference.
var pawn P; // A reference to an actor in the Pawn class.
var texture T; // A reference to a texture object.
The variable "P" above is a reference to an actor in the Pawn class. Such a variable can refer to any actor that belongs to a subclass of Pawn. For example, P might refer to a Brute, or a Skaarj, or a Manta. It can be any kind of Pawn. However, P can never refer to a Trigger actor (because Trigger is not a subclass of Pawn).
One example of where it’s handy to have a variable refering to an actor is the Enemy variable in the Pawn class, which refers to the actor which the Pawn is trying to attack.
When you have a variable that refers to an actor, you can access that actor’s variables, and call its functions. For example:
// Declare two variables that refer to a pawns.
var pawn P, Q;
// Here is a function that makes use of P.
// It displays some information about P.
function MyFunction()
{
// Set P’s enemy to Q.
P.Enemy = Q;
// Tell P to play his running animation.
P.PlayRunning();
}
Variables that refer to actors always either refer to a valid actor (any actor that actually exists in the level), or they contain the value "None". None is equivalent to the C/C++ "NULL" pointer. However, in UnrealScript, it is safe to access variables and call functions with a "None" reference; the result is always zero.
Note that an object or actor reference "points to" another actor or object, it doesn’t "contain" an actor or object. The C equivalent of an actor reference is a pointer to an object in the AActor class (in C, you’d say an AActor*). For example, you could have two monsters in the world, Bob and Fred, who are fighting each other. Bob’s "Enemy" variable would "point to" Fred, and Fred’s "Enemy" variable would "point to" Bob.
Unlike C pointers, UnrealScript object references are always safe and infallible. It is impossible for an object reference to refer to an object that doesn’t exist or is invalid (other than the special-case "None" value). In UnrealScript, when an actor or object is destroyed, all references to it are automatically set to "None".
Class reference variables
In Unreal, classes are objects just like actors, textures, and sounds are objects. Class objects belong to the class named "class". Now, there will often be cases where you'll want to store a reference to a class object, so that you can spawn an actor belonging to that class (without knowing what the class is at compile-time). For example:
var() class C;
var actor A;
A = Spawn( C ); // Spawn an actor belonging to some arbitrary class C.
Now, be sure not to confuse the roles of a class C, and an object O belonging to class C. To give a really shaky analogy, a class is like a pepper grinder, and an object is like pepper. You can use the pepper grinder (the class) to create pepper (objects of that class) by turning the crank (calling the Spawn function)...BUT, a pepper grinder (a class) is not pepper (an object belonging to the class), so you MUST NOT TRY TO EAT IT!
When declaring variables that reference class objects, you can optionally use the special class<classlimitor> syntax to limit the variable to only containing references to classes which expand a given superclass. For example, in the declaration:
var class<actor> ActorClass;
The variable ActorClass may only reference a class that expands the "actor" class. This is useful for improving compile-time type checking. For example, the Spawn function takes a class as a parameter, but only makes sense when the given class is a subclass of Actor, and the class<classlimitor> syntax causes the compiler to enforce that requirement.
As with dynamic object casting, you can dynamically cast classes like this:
class<actor>( SomeFunctionCall() )
Enumerations
Enumerations exist in UnrealScript as a convenient way to declare variables that can contain "one of" a bunch of keywords. For example, the actor class contains the enumeration EPhysics which describes the physics which Unreal should apply to the actor. This can be set to one of the predefined values like PHYS_None, PHYS_Walking, PHYS_Falling, and so on.
Internally, enumerations are stored as byte variables. In designing UnrealScript, enumerations were not seen as a necessity, but it makes code so much easier to read to see that an actor’s physics mode is being set to "PHYS_Swimming" than (for example) "3".
Here is sample code that declares enumerations.
// Declare the EColor enumeration, with three values.
enum EColor
{
CO_Red,
CO_Green,
CO_Blue
};
// Now, declare two variables of type EColor.
var EColor ShirtColor, HatColor;
// Alternatively, you can declare variables and
// enumerations together like this:
var enum EFruit
{
FRUIT_Apple,
FRUIT_Orange,
FRUIT_Bannana
} FirstFruit, SecondFruit;
In the Unreal source, we always declare enumeration values like LT_Steady, PHYS_Falling, and so on, rather than as simply "Steady" or "Falling". This is just a matter of programming style, and is not a requirement of the language.
UnrealScript only recognizes unqualified enum tags (like FRUIT_Apple) in classes where the enumeration was defined, and in its subclasses. If you need to refer to an enumeration tag defined somewhere else in the class hierarchy, you must "qualify it":
FRUIT_Apple // If Unreal can't find this enum tag...
EFruit.FRUIT_Apple // Then qualify it like this.
Structs
An UnrealScript struct is a way of cramming a bunch of variables together into a new kind of super-variable called a struct. UnrealScript structs are just like C structs, in that they can contain any simple variables or arrays.
You can declare a struct as follows:
// A point or direction vector in 3D space.
struct Vector
{
var float X;
var float Y;
var float Z
};
Once you declare a struct, you are ready to start declaring specific variables of that struct type:
// Declare a bunch of variables of type Vector.
var Vector Position;
var Vector Destination;
To access a component of a struct, use code like the following.
function MyFunction()
{
Local Vector A, B, C;
// Add some vectors.
C = A + B;
// Add just the x components of the vectors.
C.X = A.X + B.X;
// Pass vector C to a function.
SomeFunction( C );
// Pass certain vector components to a function.
OtherFunction( A.X, C.Z );
}
You can do anything with Struct variables that you can do with other variables: you can assign variables to them, you can pass them to functions, and you can access their components.
There are several Structs defined in the Object class which are used throughout Unreal. You should become familiar with their operation, as they are fundamental building blocks of scripts:
- Vector: A unique 3D point or vector in space, with an X, Y, and Z component.
- Plane: Defines a unique plane in 3D space. A plane is defined by its X, Y, and Z components (which are assumed to be normalized) plus its W component, which represents the distance of the plane from the origin, along the plane’s normal (which is the shortest line from the plane to the origin).
- Rotation: A rotation defining a unique orthogonal coordinate system. A rotation contains Pitch, Yaw, and Roll components.
- Coords: An arbitrary coordinate system in 3D space.
- Color: An RGB color value.
- Region: Defines a unique convex region within a level.
Functions
In UnrealScript, you can declare new functions and write new versions of existing functions. Functions can take one or more parameters (of any variable type UnrealScript supports), and can optionally return a value. Though most functions are written directly in UnrealScript, you can also declare functions that can be called from UnrealScript, but which are implemented in C++ and reside in a DLL. The Unreal technology supports all possible combinations of function calling: The C++ engine can call script functions; script can call C++ functions; and script can call script.
Here is a simple function declaration. This function takes a vector as a parameter, and returns a floating point number:
// Function to compute the size of a vector.
function float VectorSize( vector V )
{
return sqrt( V.X * V.X + V.Y * V.Y + V.Z * V.Z );
}
The word "function" always precedes a function declaration. It is followed by the optional return type of the function (in this case, "float"), then the function name, and then the list of function parameters enclosed in parenthesis.
When a function is called, the code within the brackets is executed. Inside the function, you can declare local variables (using the "local" keyword"), and execute any UnrealScript code. The optional "return" keyword causes the function to immediately return a value.
You can pass any UnrealScript types to a function (including arrays), and a function can return any type.
By default, any local variables you declare in a function are initialized to zero.
Function calls can be recursive. For example, the following function computes the factorial of a number:
// Function to compute the factorial of a number.
function int Factorial( int Number )
{
if( Number <= 0 )
return 1;
else
return Number * Factorial( Number – 1 );
}
Some UnrealScript functions are called by the engine whenever certain events occur. For example, when an actor is touched by another actor, the engine calls its "Touch" function to tell it who is touching it. By writing a custom "Touch" function, you can take special actions as a result of the touch occuring:
// Called when something touches this actor.
function Touch( actor Other )
{
Log( "I was touched!")
Other.Message( "You touched me!" );
}
The above function illustrates several things. First of all, the function writes a message to the log file using the "Log" command (which is the equivalent of Basic’s "print" command and C’s "printf"). Second, it calls the "Message" function residing in the actor Other. Calling functions in other actors is a common action in UnrealScript, and in object-oriented languages like Java in general, because it provides a simple means for actors to communicate with each other.
Function parameter specifiers
When you normally call a function, UnrealScript makes a local copy of the parameters you pass the function. If the function modifies some of the parameters, those don’t have any effect on the variables you passed in. For example, the following program:
function int DoSomething( int x )
{
x = x * 2;
return x;
}
function int DoSomethingElse()
{
local int a, b;
a = 2;
log( "The value of a is " $ a );
b = DoSomething( a );
log( "The value of a is " $ a );
log( "The value of b is " $ b );
}
Produces the following output when DoSomethingElse is called:
The value of a is 2 The value of a is 2 The value of b is 4
In other words, the function DoSomething was futzing with a local copy of the variable "a" which was passed to it, and it was not affecting the real variable "a".
The "out" specified lets you tell a function that it should actually modify the variable that is passed to it, rather than making a local copy. This is useful, for example, if you have a function that needs to return several values to the caller. You can juse have the caller pass several variables to the function which are "out" values. For example:
// Compute the minimum and maximum components of a vector.
function VectorRange( vector V, out float Min, out float Max )
{
// Compute the minimum value.
if ( V.X<V.Y && V.X<V.Z ) Min = V.X;
else if( V.Y<V.Z ) Min = V.Y;
else Min = V.Z;
// Compute the maximum value.
if ( V.X>V.Y && V.X>V.Z ) Max = V.X;
else if( V.Y>V.Z ) Max = V.Y;
else Max = V.Z;
}
Without the "out" keyword, it would be painful to try to write functions that had to return more than one value.
With the "optional" keyword, you can make certain function parameters optional, as a convenience to the caller. For UnrealScript functions, optional parameters which the caller doesn’t specify are set to zero. For intrinsic functions, the default values of optional parameters depends on the function. For example, the Spawn function takes an optional location and rotation, which default to the spawning actor’s location and rotation.
The "coerce" keyword forces the caller’s parameters to be converted to the specified type (even if UnrealScript normally would not perform the conversion automatically). This is useful for functions that deal with strings, so that the parameters are automatically converted to strings for you.
Function overriding
"Function overriding" refers to writing a new version of a function in a subclass. For example, say you’re writing a script for a new kind of monster called a Demon. The Demon class, which you just created, expands the Pawn class. Now, when a pawn sees a player for the first time, the pawn’s "SeePlayer" function is called, so that the pawn can start attacking the player. This is a nice concept, but say you wanted to handle "SeePlayer" differently in your new Demon class. How do you do this? Function overriding is the answer.
To override a function, just cut and paste the function definition from the parent class into your new class. For example, for SeePlayer, you could add this to your Demon class.
// New Demon class version of the Touch function.
function SeePlayer( actor SeenPlayer )
{
log( "The demon saw a player" );
// Add new custom functionality here…
}
Function overriding is the key to creating new UnrealScript classes efficiently. You can create a new class that expands on an existing class. Then, all you need to do is override the functions which you want to be handled differently. This enables you to create new kinds of objects without writing gigantic amounts of code.
Several functions in UnrealScript are declared as "final". The "final" keyword (which appears immediately before the word "function") says "this function cannot be overridden by child classes". This should be used in functions which you know nobody would want to override, because it results in faster script code. For example, say you have a "VectorSize" function that computes the size of a vector. There’s absolutely no reason anyone would ever override that, so declare it as "final". On the other hand, a function like "Touch" is very context-dependent and should not be final.
Advanced function specifiers