UDN
Search public documentation:
UnrealScriptFoundations
日本語訳
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
UE3 Home > UnrealScript Home > UnrealScript Foundation Concepts
UnrealScript Foundation Concepts
Overview
What is an UnrealScript?
.uc
file extension that contains the definition of a single UnrealScript Class. See UnrealScript Classes for more information on what a class is and how they are used by the engine as well as how to define new classes.
UnrealScripts can be created with virtually any text editor program, though some text editors do provide highlighters and other special functionality specific to UnrealScript that make working with them much easier. Certain IDEs - nFringe and WOTGreal - also provide a complete integrated solution for developing Unreal projects using UnrealScript.
Script Naming and Location
Development\Src
directory. This directory contains many folders by default - Core, Engine, UDKBase, UnrealEd, etc. - each representing a different UnrealScript project, or package. Inside each of those package folders is a Classes
folder that contains all the UnrealScripts belonging to that package.
When you want to add your own custom UnrealScripts to be used by the engine for your game, you must create one or more new package folders and subsequent Classes= folders in the ==Development\Src
directory. In these is where you will place your UnrealScripts.
Please see Custom UnrealScript Projects for more information on setting up your own custom UnrealScript packages to be used by the engine.
Anatomy of an UnrealScript
/********************* * Class Declaration * *********************/ class MyActor extends Actor; /************************************ * Instance Variables/Structs/Enums * ************************************/ enum MyEnum { ME_None. ME_Some, ME_All } struct MyStruct { var int IntVal; var float FloatVal; } var int IntVar; var float FloatVar; var bool BoolVar; var Actor ActorVar; var MyEnum EnumVar; var MyStruct StructVar; /********************** * Functions & States * **********************/ function MyFunction() { local int TempInt; if(ActorVar != none && BoolVar) { TempInt = IntVar; } } state NewState { function MyFunction() { local float TempFloat; if(ActorVar != none && BoolVar) { TempFloat = FloatVar; } } } /********************** * Default Properties * **********************/ defaultproperties { IntVar=5 FloatVar=10.0 BoolVar=true }
- Class Declaration
- Every UnrealScript begins with a class declaration. This says what the class associated with this script is named, what other class it inherits from, and allows several other aspects to be controlled through class specifiers, which are options added onto the end of the declaration. This must be the first thing in the script other than any comments you might wish to add at the beginning describing the class, specifying copyrights, etc.
- Instance Variables/Structs/Enums
- Instance variable declarations follow the class declaration. This tells the class what properties it contains.
- Functions & States
- Function and state declarations make up the majority of the class. These specify what actions the class can perform.
- Default Properties
- The
defaultproperties
block always is the last thing in the script. It provides a place to specify default values for any of the instance variables declared in the class.
Scripts and Classes vs Objects and Actors
Object
at some point and, therefore, any instance of any class is technically an object. An object has its own property values that can be modified or accessed and behaviors or actions that can be executed without affecting any other flak cannon instance in the world.
Ok, so a class is a blueprint and an object is an instance, but what does this really mean? Take the Flak Cannon, one of the weapons commonly found in Unreal Tournament, as an example. A flak cannon has ammo, fires several small projectiles as its primary fire, and fires a large shell as its alt fire. That is the (overly simplified) blueprint of a flak cannon, or the Flak Cannon class. Now, when a player runs over a pickup for a flak cannon in the world, that player is given an instance of a flak cannon. What does this really mean though? It means the Flak Cannon class is used as a blueprint to create a new flak cannon. This is a particular flak cannon with its own property values that can execute behaviors separate from any other flak cannon. So, when the player fires, the ammo count of that particular flak cannon instance is reduced and a projectile is fired from that particular flak cannon and no other.
This is the fundamental concept of object-oriented programming and something you must understand before delving into UnrealScript programming. With that said, we won't get into much more detail about it here as there are plenty of resources covering the topic already that go into great detail.
Communication Between Scripts
Pawn
it is currently controlling. This variable's type is Pawn
(and its name is coincidentally Pawn
too). The declaration for this variable looks like:
var Pawn Pawn;
none
until you assign something to it. You actually have to populate that variable with a value - the instance you want to reference. Unfortunately, there isn't a steadfast rule to follow here. There are many ways to obtain a reference to an instance of an object in the world and how you do so depends entirely on the particular situation and relationship between the two objects. Some examples are:
- Helper functions - In some cases, helper functions are provided in certain classes that return a reference to a commonly used object. For example, the
GFxMoviePlayer
class has aGetPC()
function that returns a reference to thePlayerController
that owns the movie. - Events - One actor causes an event to be fired in another, such as a
Touch
event. In many cases, the actor that triggers an event has a reference passed to that event automatically by the engine. This allows you to use that reference inside the event or saved it to a variable to be used later. - Spawning - The
Spawn()
function returns a reference to the actor it creates. When one actor spawns another and needs to be able to communicate with the spawned actor immediately or later on, you can use the reference returned by theSpawn()
function or save the reference to a variable. - 3rd Party - Quite often the object you need a reference to is already being referenced in another class to which you do have a reference to. This means you can borrow that reference to use for your own purposes. An example of this that is commonly needed and used is the current gametype, or instance of the
GameInfo
class. The only class that has a direct reference to the gametype isWorldInfo
by way of itsGame
variable, but you often need to access it in other locations. Fortunately, every Actor has a reference to the currentWorldInfo
instance via theWorldInfo
variable. This means you can use yourWorldInfo
reference to get access to the reference to theGameInfo
instance. This method is often overlooked by those just starting out. Always check the objects you do have references to as they may have references to what you are looking for. - Iteration - Using iterator functions is kind of like performing a search. They allow you to specify some criteria and return a list of results in the form of references to objects. You can act on these directly in the iterator or save them to a variable for use later.
- Editor - In some cases, you may leave it up to the level designer to set a reference to an actor in the editor. All this requires is creating an editable variable to hold the reference.
Health
variable in the Controller's Pawn, you can do so like this:
Pawn.Health
StartFire()
function:
Pawn.StartFire(0);
GetPC()
function in the GFxMoviePlayer
class discussed above. This returns the PlayerController that owns the Scaleform movie. Now let's say we want to access the health of the current player, which is held in the Pawn controlled by the PlayerController. Like above, we can use the Pawn
variable in the controller, but this time we have to reference the controller first since we are in the GFxMoviePlayer
class. We don't need to save this reference to a variable though. We can simply use the dot notation directly with the GetPC()
function call.
GetPC().Pawn.Health
GetPC().Pawn.StartFire(0);
GetPC()
can essentially be thought of as a reference to a PlayerController
instance since that is what its return value is as you can see from the declaration of the function in the GFxMoviePlayer
class:
/**
* Helper function to get the owning player controller for this movie
*
* @return The PlayerController corresponding to the LocalPlayerOwnerIndex that owns this movie
*/
event PlayerController GetPC()
{
local LocalPlayer LocalPlayerOwner;
LocalPlayerOwner = GetLP();
if (LocalPlayerOwner == none)
{
return none;
}
return LocalPlayerOwner.Actor;
}
How Do You Use Existing Scripts?
Development\Src
directory. It is important to understand what these classes are and how they can be used in your own game. The existing classes mainly provide basic and generic functionality. They are essentially part of the engine itself and not part of "the game", though this can be a fuzzy distinction at times. Many of the main systems and the classes that comprise them are explained in various "technical guides" here on the UDN. To gain a more complete understanding of all of the classes available, you may need to do some research by digging through the actual scripts themselves and analyzing the code, reading the comments, etc. An extremely useful tool in this regard is UnCodeX. It creates Javadocs-style documentation from the scripts that is easily navigable.
The first thing to understand is that you should not modify the existing scripts under normal circumstances. Modifying a native class or a class in a native package without recompiling the engine can have serious consequences, generally resulting in the engine crashing when the game is run.
UnrealScript is object-oriented which means that each class inherits - or extends in UnrealScript parlance - from another class creating a parent-child relationship. The child class inherits all of the variables, functions, states, etc. that exist in the parent class. So instead of modifying existing scripts, you should extend from them using them as starting points for your own custom classes. The beauty of this is that you are not limited to what the parent class passes down to the child class as you can add any new variables and functions you want; nor are you limited by how the parent class implemented the functions it passed down since UnrealScript allows you to override any inherited function to make it perform the exact actions you desire.
In some cases, only extending from classes and never modifying them may seem limiting as it is a common thought to add a variable to an existing base class so that it is available to all the children of that class. There is usually an alternative way to go about designing your systems that does not require modifying an existing class.
Bottom line: extend from existing classes and override functionality; never modify them.
What Class To Extend From
Object Hierarchy
- Object
- The parent class of all objects in Unreal. All of the functions in the
Object
class are accessible everywhere, because everything derives fromObject
.Object
is an abstract base class, in that it doesn't do anything useful. All functionality is provided by subclasses, such asTexture
(a texture map),TextBuffer
(a chunk of text), andClass
(which describes the class of other objects). - Actor (extends Object)
- 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 (extends Actor)
- The parent class of all creatures and players in Unreal which are capable of high-level AI and player controls.
- Class (extends Object)
- 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.
UnrealScript Programming Strategy
- UnrealScript is a slow language compared to C/C++. A typical C++ program runs about 20X faster than UnrealScript. The programming philosophy behind all of our own script writing is this: Write scripts that are almost always idle. In other words, use UnrealScript only to handle the "interesting" events that you want to customize, not the rote tasks, like basic movement, which Unreal's physics code can handle for you. For example, when writing a projectile script, you typically write a
HitWall()
,Bounce()
, andTouch()
function describing what to do when key events happen. Thus 95% of the time, your projectile script isn't executing any code, and is just waiting for the physics code to notify it of an event. This is inherently very efficient. In our typical level, even though UnrealScript is comparably much slower than C++, UnrealScript execution time averages 5-10% of CPU time. - Exploit latent functions (like FinishAnim and Sleep) as much as possible. By basing the flow of your script execution on them, you are creating animation-driven or time-driven code, which is fairly efficient in UnrealScript.
- Keep an eye on the Unreal log while you're testing your scripts. The UnrealScript runtime often generates useful warnings in the log that notify you of nonfatal problems that are occurring.
- Be wary of code that can cause infinite recursion. For example, the "Move" command moves the actor and calls your Bump() function if you hit something. Therefore, if you use a Move command within a Bump function, you run the risk of recursing forever. Be careful. Infinite recursion and infinite looping are the two error conditions which UnrealScript doesn't handle gracefully.
- Spawning and destroying actors are fairly expensive operations on the server side, and are even more expensive in network games, because spawns and destroys take up network bandwidth. Use them reasonably, and regard actors as "heavy weight" objects. For example, do not try to create a particle system by spawning 100 unique actors and sending them off on different trajectories using the physics code. That will be sloooow.
- Exploit UnrealScript's object-oriented capabilities as much as possible. Creating new functionality by overriding existing functions and states leads to clean code that is easy to modify and easy to integrate with other peoples' work. Avoid using traditional C techniques, like doing a switch() statement based on the class of an actor or the state, because code like this tends to break as you add new classes and modify things.
- UnrealScript .u packages are compiled strictly in the order specified by the .ini file's EditPackages list, so each package can only reference other objects in itself and in previously-compiled packages, and never in subsequently-compiled packages. If you find that a need for circular references between packages arises, the two solutions are:
- Factor the classes into a set of base classes compiled in the first .u package, and a set of child classes compiled in the second .u package, making sure that the base classes never reference the child classes. This is a good programming practice anyway, and it usually works.
Note: If a given classC
needs to reference a class or objectO
in a later-compiled package, you can often factor that class into two parts: an abstract base class definitionC
that defines a variableMyO
in the first package (but doesn't contain a default value forMyO
in its default properties), and a subclassD
in the second package that specifies the proper default value forMyO
which can only be done from within that second package. - If the two .u packages are inextricably intertwined by references, then merge them into a single package. This is reasonable because packages are intended as a unit of code modularity, and there's not a real benefit (such as memory savings) to separating these inseparable sets of classes into multiple packages.
- Factor the classes into a set of base classes compiled in the first .u package, and a set of child classes compiled in the second .u package, making sure that the base classes never reference the child classes. This is a good programming practice anyway, and it usually works.