This is where I'm going to put all the information that I wish someone had told me when I started writing mods for Unreal. A lot of this information may not be relevant to you until you have more experience with the engine. I spent a lot of time out on my front porch with a buddy (Sel Tremble) talking about things like replication trying to figure out exactly how it all worked. That was definitely one of the most satisfying things I have ever done. Cracking open a new game can be a very cool experience, but also a very frustrating one. Here I'll give you a couple pointers to ease your exploration.
Contents
Accessed Nones
Sooner or later these will start showing up in your log files. UnrealScript treats Accessed Nones as warnings but you should treat them as errors. Accessed Nones are easy to fix and always signal that something is wrong with your code. If you are familiar with C++ or Java, it's easy to figure out what an Accessed None is. I'll briefly explain them to people who aren't so familiar.
UnrealScript is an object oriented programming language. When you write a program in UnrealScript, you define a set of behavior for these objects to obey and how they will interact. An object has a set of properties: member variables and member functions. In order to access an object property, you need a reference to that object. Let's look at some sample code:
class MyObject extends Info; var PlayerReplicationInfo PlayerInfo; function PlayerReplicationInfo TestFunction() { return PlayerInfo; }
Here we have a simple object called MyObject
that is a subclass of Info (UT). It has two properties: a variable called PlayerInfo and a function called TestFunction. You might want to interact with this object from inside your mod. Let's say you have a reference to a MyObject inside your mod and you want to get some information from inside the PlayerInfo property. You might write code that looks like this:
class MyMod extends TournamentGameInfo; function string GetPlayerName() { local MyObject Object1; local string PlayerName; Object1 = GetMyObject(); PlayerName = Object1.PlayerInfo.PlayerName; Log("The player's name is"@PlayerName); }
In this example we call a function called GetMyObject()
to get a reference to a MyObject. We then access that reference to resolve PlayerInfo (Object1.PlayerInfo
) and then access the PlayerInfo reference to resolve PlayerName (PlayerInfo.PlayerName
). But what if there isn't a MyObject available, or a bug in GetMyObject()
causes it to fail to return a MyObject? In that case, the function would return None. None is an empty reference... a lot like a NULL pointer in C++.
If, in our example, GetMyObject()
returns None, then the variable Object1 is assigned None. In the next line, we try and access Object1 to resolve the PlayerInfo reference. Uh oh... Object1 is None... it doesn't refer to anything. We can't access it, so the Unreal engine logs a warning saying the code broke: "Accessed None in MyMod.GetPlayerName!"
Its very easy to avoid buggy code like this. Just add a couple checks to your code and define special behavior in the case of a mistake:
class MyMod extends TournamentGameInfo; function string GetPlayerName() { local MyObject Object1; local string PlayerName; Object1 = GetMyObject(); if ((Object1 != None) && (Object1.PlayerInfo != None)) PlayerName = Object1.PlayerInfo.PlayerName; else PlayerName = "Unknown"; Log("The player's name is"@PlayerName); }
Now we are checking to see if Object1 is none and then checking to see if the PlayerInfo reference is none. if
statements in UnrealScript use short circuit logic. That is, if
statements are evaluated from left to right. As soon as the code encounters a statement that negates the if
, it stops evaluating. That means that if Object1 is None, the code will never evaluate the (Object1.PlayerInfo != None)
statement. It knows that it doesn't matter what the rest of the statement says, the first part is false so the entire statement is false.
Accessed Nones can be especially dangerous in time critical functions like Timer and Tick. It takes a lot of time to write out an error message to the log and if your code is dumping 3000 error messages a second it can really kill performance (not to mention disk space).
Dangerous Iterators
UnrealScript implements a very useful programming tool called iterators. An iterator is a datatype that encapsulates a list. (UnrealScript only supports list iterators, our next language will support user defined iterators). You can get an iterator and loop on it, performing an operation on every object inside the iterator. Here is an example:
local Ammo A; foreach AllActors(class'Ammo', A) { A.AmmoAmount = 999; }
In this example we are using the AllActors function to get an actor list iterator. We then use the foreach iterator loop to perform some behavior on every object the AllActors function returns. AllActors takes the class of the type of actor you want and a variable to put it in. AllActors will search through every actor in the current game for the objects you want. Here we are saying "Set the AmmoAmount of every actor in the game to 999." Sounds pretty cool, but lets think about it. We are searching through a list of hundreds of Actors for a small few. This isn't exactly a fast operation.
Iterators can be extremely useful if used carefully. Because they tend to be slow, you'll want to avoid performing iterations faster than a couple times a second. Never perform an AllActors iteration inside of Tick() and never perform AllActors iterations inside of other loops. (Okay, so saying never is a little strict. Let's say... use your best judgement...)
The most common type of AllActors search you'll work with will probably be a search for all of the PlayerReplicationInfo actors. PlayerReplicationInfo contains important information about Players that the server sends to each client. It allows each client to have an idea of the status of other players without sending too much information. Its used to show the scores on the scoreboard and other common things.
Usually, there will only be a handful of PlayerReplicationInfo actors in the global actor list. It doesn't really make sense to do a time consuming search for so few results. In order to simplify this common iteration, we've added a PRI array to GameReplicationInfo. Every tenth of a second, the PRIArray is updated to contain the current set of PlayerReplicationInfos. You can then do your operation of the PRIArray without having to do an AllActors call.
Other iterators are also available. Look in the Actor class definition for information. They do exactly what they sound like: TouchingActors returns touching actors, RadiusActors returns all the actors in the given radius, etc. Intelligent use of these iterators will help you keep your code fast.
The Foibles of Tracing
Wahaha. I just wanted to use the word foible.
Because the Unreal engine does not use a potentially visible set, if you want to find something in the world in a spacial sense, you'll need to perform a trace. Most of the time you'll have a good idea of where you are tracing, you just want to know whats on the other end of the line. Other times, you'll use a series of traces to get an idea of what surrounds the object in question.
My first advice is to avoid traces wherever possible. Think very hard about what you are using the trace for and try to come up with an alternate way of doing it. Traces are expensive operations that can introduce subtle slowdowns into your mod. You might have a player doing a couple traces every tick and during your testing everything is fine. What you don't realize, is that as soon as you are playing online with 15 of your buddies, those traces start to add up.
If you have to perform traces, limit their size. Shorter traces are faster than long traces. If you are designing a new Shotgun weapon for UT, for example, you might want to perform 12 traces when the weapon is fired to figure out the scatter of the gun. 12 traces is perfectly reasonable.... it's not like the player is going to be firing his shotgun 30 times a second. However, those 12 traces could get expensive if your mod uses large open levels. Its highly unlikely your shotgun is going to be very useful as a long-range weapon, so you might as well cut off its range at a certain point. It saves the engine from having to trace from one end of the map to the other in the worst case.
Using traces is ultimately a judgment call. It really only becomes a big problem when you perform a lot of traces in a single frame. Nonetheless, it's definitely something to keep your eyes on. Always think about the performance implications of code you write.
Decrypting Replication
Understanding replication is one of the most difficult aspects of writing a mod, but its utterly necessary if you plan on doing any netplay at all. Unfortunately, Tim's replication docs are not easy to understand and make some assumptions about the reader's knowledge that you may not possess. I'll try to point out the things that I learned only through trial and error.
Simulated functions are called on both the client and the server. But only if called from a simulated function. As soon as a function call breaks the simulation chain, the simulation stops. Be very aware of what you are simulating and what you are doing in simulated functions. Never add a function modifier like simulated just because you saw it in the Unreal source code somewhere else. Understand why you are adding it, know what it does. You can't possibly expect to write quality mods if you don't know what your code is doing.
Because a simulated function is called on both the client and the server you have to be particularly aware of what data you are accessing. Some object references that are available on the server might not be available on the client. For example, every Actor has a reference to the current level. Inside the level reference is a reference to the current game. You might write code that looks like this:
simulated function bool CheckTeamGame() { return Level.Game.bTeamGame }
This is a simple simulated function that returns true or false depending on whether or not the current game is a team game. It does this by checking the bTeamGame property of the current level's GameInfo (UT) reference. What's wrong with this picture?
The Game property of the Level reference is only valid on the server. The client doesn't know anything about the server's game object so the client will log an Accessed None. Yuck!
If you open up the script for LevelInfo, you can find a section that looks like this:
//----------------------------------------------------------------------------- // Network replication. replication { reliable if( Role==ROLE_Authority ) Pauser, TimeDilation, bNoCheating, bAllowFOV; }
The replication block is a special statement that tells the Unreal engine how to deal with the properties of this object. Lets look at it closely.
First, we have a replication condition: reliable if( Role == ROLE_Authority)
. The first part of the condition will either be reliable or unreliable. If it says reliable, that means the engine will make sure the replicated information gets to each client safely. Because of the way the UDP protocol works, its possible for packets to get lost in transmission. Unreliable replication won't check to see if the packet arrived safely. Reliable replication has a slightly higher network overhead than unreliable replication.
The second part of the condition (Role == ROLE_Authority)
tells the engine when to send the data. In this situation we are going to send the data whenever the current LevelInfo object is an Authority. To really decypher what this means you have to understand the specific role of the object in question. With a LevelInfo, the server is going to maintain the authoritative version of the object. The server tells the clients how the level is behaving, not the other way around. For our example replication block, this means that the data will be sent from the server to each client.
The other common type of condition is (Role < ROLE_Authority)
. This means that the engine should send the data when the current object is not an authority. Or rather, that the client should tell the server the correct information.
Finally, we see four variables listed beneath the condition. These are the variables that the statement applies to. In this situation, we have a statement saying, "If we are the server and the client has an outdated copy of these variables, then send to the client new information about Pauser, TimeDilation, bNoCheating, and bAllowFOV. Always make sure the data arrives safely."
The replication statement doesn't cover the rest of the variables in the LevelInfo. This can mean two things. Either the information is filled in by the client in C++ (in the case of TimeSeconds) or the information is never updated on the client and is completely unreliable (in the case of Game).
You don't have access to the C++ code, but you can make a couple inferences about an object's properties to help you determine whether or not a class has non-replicated properties that are filled in my C++. Look at the class declaration for LevelInfo:
class LevelInfo extends ZoneInfo native;
Native means "This object is declared in C++ and in UnrealScript." Native classes probably have behavior in C++ that you can't see. Only a few special classes are native.
Finally, watch out for classes that say nativereplication
in the class declaration. This means that the replication
block inside UnrealScript doesn't do anything and that replication is entirely defined in C++. Some network heavy objects use native replication to help with network performance.
So now you have an idea of how to avoid problems with simulated functions. Now lets look at replicated functions.
A replicated function is a function that is called from the client or the server but executed on the other side. An example of a replicated function is the Say
function. When you hit the T key to talk to everyone in a game, you are actually executing the Say function along with whatever you said. The client takes the function and its parameters and sends it to the server for execution. The server then broadcasts your message to all the other clients.
Replicated functions are very easy to use if you remember one thing: They can't return a value. A replicated function is transmitted over the network to the other side... that takes time (approximately equal to your ping). If replicated functions were blocking (i.e.: they waited for a return value) network communication would halt.
This is obvious for anyone who thinks about it, but when you are working on your mod you might not think about it. Replicated functions return immediately. Use them to trigger behavior on the client (like special effects) or send a message (a weapon fire message to the server).
Finally, replicated functions are restricted to only a few classes. A function call on an actor can only be replicated to the player who owns that actor. A function call can only be replicated to one actor (the player who owns it); they cannot be multicast. You might use them with weapons or inventory items you make (where the function is replicated to the player who owns the item).
Okay, so that should help you get into replication... let's move on.
Don't use UnrealEd
UnrealEd is a great editor for developing levels, but probably not the best place to work on code. This is a judgment call. I use Microsoft Developer Studio as my editor and ucc make
to compile the package files. I find the Find In Files option in Dev Studio to be very useful and the editor to be very powerful.
In addition, UnrealEd hides the default properties blocks of source files, making them only accessible through the Show Defaults option. This just sucks! To export the script files to disk, go to the script browser and hit the "Export All" button. The files will be exported to their package directories ready for you to browse.
If UnrealEd crashes with a DLL or OCX error of some sort, go to unreal.epicgames.com and click on Downloads. Download the latest Unreal Editor Fix. The current fix level is 4.
Prev Page: Legacy:Mod Authoring/The Three Mod Types – Section 4 of 12 – Next Page: Legacy:Mod Authoring/Setting Up Your Project