Chapter 9. Debugging and Optimization

PC Load Letter? What does that mean?

The problem with computers is that they're not psychics. They have no idea what you're trying to accomplish. With any programming language, my mantra has always been this: Computers will never do what you want them to do. They will do exactly what you tell them to do. A lot of the time what you're telling them to do isn't what you want them to do, which causes unexpected bugs, errors, even crashes. Knowing a programming language is one thing, but the most important skill in programming is having the critical thinking skills to figure out why a program is broken and being able to fix it. Most of the code you write will not work correctly the first time. Things will break. A lot!

In this chapter we will:

  • Cover some of the most common errors that you'll encounter while compiling UnrealScript

  • Take a look at some broken code to see if we can fix it

  • Use the log to debug and further clean up our code

  • Use the profiler to minimize performance hits from our code

So with that, let's break stuff!

Compiler errors

While getting used to any programming language, you will inevitably encounter a lot of errors trying to compile or run your code, most often from syntax errors. Write the code slightly wrong and nothing will work, even if it compiles. In this section, we'll take a look at some of the most common errors you'll encounter working with UnrealScript, what they mean, and how to fix them. Let's get started!

Time for action Preparing for brokenness

Before we break anything, we need to set up a new map and a new script folder specifically for these experiments. We're not going to use AwesomeGame because we want to focus specifically on the errors we create, and we don't want any interference from other code:

  1. Create a copy of AwesomeTestMap.udk in the UDKGameContentMapsAwesomeGame folder and call it BrokenMap.udk.

  2. Open BrokenMap.udk in the editor.

  3. Delete all of the Kismet, plus all of the AwesomeEnemySpawners and other actors on the map apart from PlayerStart, the ground, and the lights.

  4. Save and close the map.

What just happened?

Now we have the map set up for testing. Next we're going to create a new script folder specifically for these tests.

Time for action A new script package

It's been awhile since we set up a script folder, so let's go through the steps again:

  1. Create a new folder in the DevelopmentSrc folder called BrokenGame.

  2. Create a folder inside BrokenGame called Classes.

  3. In the BrokenGameClasses folder, create a new file called BrokenActor.uc.

  4. In BrokenActor.uc, write the following code:

    class BrokenActor extends Actor
    placeable;
    defaultproperties
    {
    Begin Object Class=SpriteComponent Name=Sprite
    Sprite=Texture2D'EditorMaterials.TargetIcon'
    Scale=0.35
    HiddenGame=true
    End Object
    Components.Add(Sprite)
    }
    
  5. Before we can compile, we need to add our new package to DefaultEngine.ini. Open UDKGameConfigDefaultEngine.ini, and at the end of the [Engine.ScriptPackages] section add our new package:

    [Engine.ScriptPackages]
    +NonNativePackages=UTGame
    +NonNativePackages=UTGameContent
    +NonNativePackages=AwesomeGame
    +NonNativePackages=BrokenGame
    
  6. Also at the end of the [UnrealEd.EditorEngine] section:

    [UnrealEd.EditorEngine]
    +EditPackages=UTGame
    +EditPackages=UTGameContent
    +EditPackages=AwesomeGame
    +EditPackages=BrokenGame
    
  7. Save and close DefaultEngine.ini.

  8. Back in ConTEXT, compile our code.

  9. Once it compiles successfully, open BrokenMap in the editor.

  10. Select BrokenActor in the Actor Classes tab of the Content Browser (it will be in the Uncategorized section if you have Show Categories checked), and place one in the map near the player start.

  11. Save the map, but don't close the editor yet.

  12. Let's add a PostBeginPlay function to our class:

    simulated function PostBeginPlay()
    {
    `log("BrokenActor PostBeginPlay!");
    }
    
  13. Compile the code.

What just happened?

Wow that's ugly. Here we have our first compiler error, but luckily it's also the easiest to fix.

[0005.15] Log: Warning/Error Summary
[0005.15] Log: ---------------------
[0005.15] Log: Error, Error deleting file 'R:UDKUDK-AwesomeGameBinariesWin32....UDKGameScriptBrokenGame.u' (GetLastError: 32)
[0005.15] Log: Error, Error saving '....UDKGameScriptBrokenGame.u'
[0005.15] Log: Warning, Failed to delete ....UDKGameScriptBrokenGame.u
[0005.15] Log: Warning, DeleteFile was unable to delete 'R:UDKUDK-AwesomeGameBinariesWin32....UDKGameScriptBrokenGame.u', retrying... (GetLastE-r-r-o-r: 32)
[0005.16] Log:
[0005.16] Log: Failure - 3 error(s), 3 warning(s)

A lot of references to being unable to delete BrokenGame.u. If you ever get an error like this, make sure the editor is closed and no instances of the game are running before you compile. With any of those running, BrokenGame.u will be in use and the compiler won't be able to delete the old .u file so it can replace it with the new one. With that in mind, close the editor and compile the code again.

[0004.11] Log: Success - 0 error(s), 0 warning(s)

Much better. It's a bit of a pain, but while you're working on your game you will have to close the editor before you compile. Luckily for us this is the scariest looking error to encounter while working with UnrealScript. Most of them will be simple and pretty self-explanatory.

Time for action Breaking the class itself

Another common mistake is a class name mismatch. Let's take a look.

  1. At the top of our class, let's change the class declaration:

    class Borked extends Actor
    placeable;
    
  2. Now try to compile the code. We'll get this in the log:

    [0004.13] Log: R:UDKUDK-AwesomeGameBinaries..DevelopmentSrcBrokenGameClassesBrokenActor.uc : Error, Script vs. class name mismatch (BrokenActor/Borked)
    

What just happened?

The names of the classes in each file must match the file name. In our case, since the file is called BrokenActor.uc, the class must be declared as BrokenActor. Let's change it back:

class BrokenActor extends Actor
placeable;

It's a minor error, but you may encounter it from time to time. It's easy to forget to rename a class if you've copied it from another of your projects, for example.

Another common error that breaks the class file itself is saving the text file in the wrong format. If you ever encounter an error such as this:

[0004.03] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(1) : Error, Unexpected 'ï'

The i with the umlaut is part of the UTF-8 file encoding, so if you get an error like this make sure your .uc files are saved with ANSI encoding. You can double-check this by opening the file in Notepad and looking at the Encoding drop-down list in the Save As dialogue.

Now that we've broken the class, let's break some code!

Time for action Breaking some more code

Let's take a look at two more common errors when working with UnrealScript. We'll add a bit more to our BrokenActor.

  1. Let's change our BrokenActor to look like this:

    class BrokenActor extends Actor
    placeable;
    var int MyInt;
    simulated function PostBeginPlay()
    {
    if(MyInt > 5)
    {
    `log("MyInt is greater than 5! MyInt is:" @ MyInt);
    }
    else
    {
    `log("MyInt is less than or equal to 5! MyInt is:" @ MyInt);
    }
    defaultproperties {
    MyInt=13
    Begin Object Class=SpriteComponent Name=Sprite
    Sprite=Texture2D'EditorMaterials.TargetIcon'
    Scale=0.35
    HiddenGame=true
    End Object
    Components.Add(Sprite)
    }
    
  2. Compile the code. We'll see this error pop up:

    [0003.94] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(13) : Error, Unexpected end of file at end of Class
    
  3. Whenever this error shows up, it means we've missed a closing } bracket somewhere. In this case, we haven't properly ended our PostBeginPlay function with one, so let's add it:

    simulated function PostBeginPlay()
    {
    if(MyInt > 5)
    {
    `log("MyInt is greater than 5! MyInt is:" @ MyInt);
    }
    else
    {
    `log("MyInt is less than or equal to 5! MyInt is:" @ MyInt);
    }
    }
    

    Now let's compile the code. It works this time!

  4. Since we already have BrokenActor placed in our map, we don't need to open the editor for this next step. Instead let's create a copy of our Awesome Test Map.bat batch file and name it Broken Map.bat.

  5. Open Broken Map.bat and change it to look like this:

    C:UDKUDK-AwesomeGameBinariesWin32UDK.exe BrokenMap?GoalScore=0?TimeLimit=0 -log
    

    The map name has changed, but also note that we're not running it with AwesomeGame anymore. Goodbye AwesomeGame! You have served us well.

  6. Save and close Broken Map.bat.

  7. Double-click the batch file to run the game. While it's loading think about what we should see in the log. We've set MyInt to 13 in the default properties, so we should see the first log in our script show up. We'll see this isn't the case though:

    [0005.20] ScriptLog: MyInt is less than or equal to 5! MyInt is: 0
    
  8. Well what happened there? Let's change our default properties block. Right now it looks like this:

    defaultproperties {
    MyInt=13
    Begin Object Class=SpriteComponent Name=Sprite
    Sprite=Texture2D'EditorMaterials.TargetIcon'
    Scale=0.35
    HiddenGame=true
    End Object
    Components.Add(Sprite)
    }
    

    Let's move the first bracket down to the next line, so it looks like this instead:

    defaultproperties
    {
    MyInt=13
    Begin Object Class=SpriteComponent Name=Sprite
    Sprite=Texture2D'EditorMaterials.TargetIcon'
    Scale=0.35
    HiddenGame=true
    End Object
    Components.Add(Sprite)
    }
    
  9. Now let's compile the code and run the game again:

    [0005.40] ScriptLog: MyInt is greater than 5! MyInt is: 13
    

Much better!

What just happened?

Misplacing or missing brackets, that is. { } are fairly common for new programmers. To avoid this problem, it's best to create both of them at the same time, before you start writing code inside them.

The second problem with the default properties is more common among programmers who are used to other languages and who format their code differently. The rest of an UnrealScript class can be written with the opening bracket on the same line as the function declaration:

function Something() {
SomeCode();
}

The default properties block is the only place where you can't do this, the opening bracket has to be on a new line, like this:

defaultproperties
{
SomeVariable=2
}

Time for action Misleading errors

Sometimes error messages might seem a little misleading, as we'll see in this next experiment:

  1. Let's rewrite our PostBeginPlay function. We'll leave in our MyInt variable and use it for this experiment:

    simulated function PostBeginPlay()
    {
    if(UTGame(WorldInfo.Game != none)
    MyInt = UTGame(WorldInfo.Game).DefaultMaxLives;
    }
    
  2. Seems fine, but when we try to compile we get this error:

    [0004.84] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(8) : Error, Bad or missing expression for token: UTGame, in 'If'
    

    What could this mean? It seems to be telling us that it doesn't know what UTGame is, even though it's a valid class. If we take a closer look at our if statement, we can see the problem:

    if(UTGame(WorldInfo.Game != none)
    

    I'm counting two open parentheses, but only one closed one. If we take the if part out of this line and examine it separately:

    UTGame(WorldInfo.Game != none)
    

    To the compiler, it looks like we're trying to call a function called UTGame and that we're trying to send this function a bool (WorldInfo.Game != none). Since it can't find a function called UTGame, it gives us this error:

    [0004.84] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(8) : Error, Bad or missing expression for token: UTGame, in 'If'
    

    So the error message might seem misleading, but to the compiler it makes perfect sense.

  3. Let's fix the error by adding the closing parenthesis:

    simulated function PostBeginPlay()
    {
    if(UTGame(WorldInfo.Game) != none)
    MyInt = UTGame(WorldInfo.Game).DefaultMaxLives;
    }
    
  4. Now it compiles just fine.

What just happened?

It would be nice if the compiler just said You're missing a closing parenthesis here, but remember the mantra: The computer is just doing exactly what you told it to do. You're trying to typecast to UTGame, but without the parenthesis you've told the code to try to call a function.

As with brackets, when working with parentheses it's best to write both of them at the same time, and then fill them in afterward. This can be especially important when working with complicated math equations.

Sometimes the errors are easy to figure out, as we'll see in our next experiment.

Time for action Captain obvious to the rescue!

Sometimes we'll get lucky and the compiler error message will trigger an Oh, durhey moment the second we see it.

  1. Let's rewrite PostBeginPlay function again:

    simulated function PostBeginPlay()
    {
    MyInt = class'UTGame'.default.CountDown
    }
    
  2. Now let's try to compile.

    [0003.91] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(9) : Error, Missing ';' before '}'
    
  3. Oh, duh, we forgot the semicolon at the end of the line.

    simulated function PostBeginPlay()
    {
    MyInt = class'UTGame'.default.CountDown;
    }
    
  4. Now it compiles!

What just happened?

Sometimes compiler errors are really self explanatory. Let's take a look at another one.

Time for action Setting up a twofer

Another obvious error, with a twist!

  1. Let's rewrite our PostBeginPlay function:

    simulated function PostBeginPlay()
    {
    var int AnotherInt;
    AnotherInt = class'UTGame'.default.CountDown;
    }
    
  2. Looks fine this time, we have our brackets, parentheses, and semicolons in place. What could go wrong?

    [0003.93] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(8) : Error, Instance variables are only allowed at class scope (use 'local'?)
    
  3. Oh, right. Inside functions we're only allowed to use local variables. Let's fix that real quick:

    simulated function PostBeginPlay()
    {
    local int AnotherInt;
    AnotherInt = class'UTGame'.default.CountDown;
    }
    
  4. Now it should work, so let's compile it:

    [0004.08] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(10) : Warning, 'AnotherInt' : unused local variable
    
  5. Well this is new, a warning this time instead of an error! Unused local variable. We're assigning a value to it, but we're never actually using it anywhere. Warnings aren't game breaking and they won't prevent the code from compiling, they're more helpful hints. In this case it's saying hey, if you're not doing anything with this variable do you really need it at all? We can delete it and the line assigning it a value, but instead let's just use it in a log:

    simulated function PostBeginPlay()
    {
    local int AnotherInt;
    AnotherInt = class'UTGame'.default.CountDown;
    `log("AnotherInt is:" @ AnotherInt);
    }
    
  6. This time it compiles fine, no errors, and no warnings.

What just happened?

Missing semicolons is another easy error to fix. You also have to watch out for extra semicolons. Take a look at the following code:

var int MyInt;
simulated function PostBeginPlay()
{
if(MyInt < 5);
MyInt = class'UTGame'.default.CountDown;
`log("MyInt is:" @ MyInt);
}
defaultproperties
{
MyInt=13
}

It seems like it should be logging 13 since MyInt is greater than 5 and the assignment inside the if statement shouldn't execute, but running it gives us this:

[0009.32] ScriptLog: MyInt is: 4

The default value for CountDown in UTGame is 4, so that part is being executed. But why? The problem is in our if statement:

if(MyInt < 5);

The semicolon at the end completes the if statement, essentially making it do nothing. The code treats the next line as separate from the if statement, so it will always execute with this code. Written correctly it should look like this:

if(MyInt < 5)
MyInt = class'UTGame'.default.CountDown;

Next we'll take a look at some things that can go wrong when working with functions.

Time for action Mal-function

First, we'll take a look at what can go wrong in function declarations.

  1. Let's add a new function to our class:

    simulated function HitWall()
    {
    }
    
  2. Compile the code and we'll get this error:

    [0003.93] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(4) : Error, Redefinition of 'function HitWall' differs from original; different number of parameters
    
  3. If we look at Actor.uc we can see the original declaration of this function:

    event HitWall( vector HitNormal, actor Wall, PrimitiveComponent WallComp )
    
  4. When overriding a function, you must use the same parameters as the original. You can change the names if you want, for instance this would work:

    simulated function HitWall( vector Norm, actor HitActor, PrimitiveComponent Prim )
    {
    }
    

    The number and type of parameters must stay the same though.

    This type of error usually results from misreading the parameters when overriding a function, or accidentally making up a function that already exists in a superclass.

  5. The same thing can happen with return values. Let's rewrite our HitWall function to look like this:

    simulated function bool HitWall( vector Norm, actor HitActor, PrimitiveComponent Prim )
    {
    }
    
  6. Compile the code and we'll get this error:

    [0003.84] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(4) : Error, Redefinition of 'function HitWall' differs from original: return value mismatch
    

    This is most commonly caused by not including the return value when you write your function.

  7. Along the same lines, we can accidentally leave out the return value for a function. Let's delete HitWall and add this made up function:

    function bool AmIAwesome()
    {
    return;
    }
    
  8. If we try to compile the preceding code we'll get this error:

    [0003.82] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Bad or missing expression in 'Return'
    

    Note

    When working with functions that have a return value, always make sure you're returning the right type of variable.

  9. Let's change the function to look like this:

    function bool AmIAwesome()
    {
    return 5;
    }
    
  10. Trying to compile this code would give us a different error stemming from the same problem of the wrong type of return value:

    [0003.84] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Type mismatch in 'Return'
    

What just happened?

These types of errors are pretty obvious, and easy to fix. Just make sure to take careful note of any parameters and the return type of functions you're using.

We also need to be careful when we're calling other functions, to make sure that we're passing the correct number and type of variables. If we had this code:

simulated function PostBeginPlay()
{
TakeDamage(5.0);
}

we would get this error when we compiled:

[0003.89] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Call to 'TakeDamage': missing or bad parameter 2

Taking a look at the original declaration of TakeDamage in Actor, we can see the correct number and type of variables that we need to pass to this function:

event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)

The last two are optional, but the first five need to be included in our call to this function.

There are a few other errors that we need to watch out for when dealing with functions.

Time for action Taking care of other function errors.

Let's take a look at some more function errors:

  1. Say we had the following code:

    simulated function PostBeginPlay()
    {
    `log("PostBeginPlay!");
    }
    simulated function PostBeginPlay()
    {
    local int SomeInt;
    SomeInt = 5;
    `log("SomeInt is:" @ SomeInt);
    }
    
  2. The code seems fine, but if we compile it we get:

    [0003.86] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(9) : Error, 'PostBeginPlay' conflicts with 'Function BrokenGame.BrokenActor:PostBeginPlay'
    
  3. This one's easy to avoid. Classes can only have one function with any one name; trying to have more than one with the same name will give us an error.

  4. This next one's a bit obscure, since static functions aren't used that much. Suppose we had this code:

    static function float GetRadius()
    {
    local float Radius, Height;
    GetBoundingCylinder(Radius, Height);
    return Radius;
    }
    
  5. Trying to compile that would give us this error:

    [0003.87] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(7) : Error, Can't call instance functions from within static functions
    

What just happened?

Remembering that static functions are called on the class without the need for the actor to exist in the world, it makes sense that we wouldn't be able to call normal functions in them. Static functions are only able to call other static functions, but normal functions can call static functions.

Now let's take a look at some errors that can result when typecasting Actor variables.

Time for action Actor variable errors

Errors of this type are common when you're getting used to UnrealScript. Knowing when and how to use typecasting and dealing with Actor variables takes some time to get used to. Let's see some of the errors we can come across when doing this:

  1. Let's say this Actor killed anyone who touched it while holding a weapon. We might have a function that looked something like this:

    event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
    {
    if(Other.Weapon != none)
    Other.Suicide();
    }
    
  2. Compiling this code gives us an error:

    [0003.90] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Unrecognized member 'Weapon' in class 'Actor'
    
  3. What's causing this error? If we search Actor.uc we won't find a Weapon variable; what we meant to do is check if the actor bumping us is a Pawn, and check if that Pawn has a weapon. Let's rewrite the function a bit:

    event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
    {
    if(Pawn(Other) != none && Pawn(Other).Weapon != none)
    Other.Suicide();
    }
    

    Now we're checking if Other is Pawn and if it is, if it has Weapon.

  4. If we try to compile now we'll receive another error message:

    [0003.87] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(7) : Error, Unrecognized member 'Suicide' in class 'Actor'
    
  5. We're running into the same problem here, Actor doesn't have a function called Suicide, but Pawn does. Let's change the function again:

    event Bump( Actor Other, PrimitiveComponent OtherComp, Vector HitNormal )
    {
    if(Pawn(Other) != none && Pawn(Other).Weapon != none)
    Pawn(Other).Suicide();
    }
    
  6. Compiling this time works fine.

  7. Of course, the opposite problem is also true. Let's take a look at this function:

    function KilledBy( Pawn EventInstigator )
    {
    if(Pawn(EventInstigator) != none)
    Pawn(EventInstigator).Suicide();
    }
    
  8. If we compile that, we get this error:

    [0003.88] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Cast from 'Pawn' to 'Pawn' is unnecessary
    
  9. We don't need to typecast if the variable is already the type of variable we need. If we rewrote it like this:

    function KilledBy( Pawn EventInstigator )
    {
    if(Actor(EventInstigator) != none)
    Actor(EventInstigator).Suicide();
    }
    

    We would get the same error:

    [0003.89] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Cast from 'Pawn' to 'Actor' is unnecessary
    
  10. The correct way to write this particular function would be:

    function KilledBy( Pawn EventInstigator )
    {
    if(EventInstigator != none)
    EventInstigator.Suicide();
    }
    

What just happened?

Typecasting is only necessary when you need to access variables and functions of a subclass of an Actor variable, otherwise you already have access to them because of inheritance, so typecasting is unnecessary.

Another thing to look out for when typecasting is that the class you're casting to is a subclass of the one you have. If it's somewhere else in the class tree you'll get another error. Let's look at this code:,

function KilledBy( Pawn EventInstigator )
{
if(UTGame(EventInstigator) != none)
EventInstigator.Suicide();
}

Since UTGame is not a subclass of Pawn, and is nowhere near it on the class tree, we'll get this error when we compile:

[0003.84] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(6) : Error, Cast from 'Pawn' to 'UTGame' will always fail

What this is telling us is that since the class we're trying to cast to isn't a subclass of Pawn, the cast will always return none.

Time for action Other variable errors

There are a few more errors we need to take a look at before we move on. These ones have to do with the declaration and use of variables.

  1. Let's take a look at this PostBeginPlay function:

    simulated function PostBeginPlay()
    {
    local int Int1, Int2;
    Int1 = 3;
    Int2 = 5;
    Int1 + Int2;
    }
    
  2. If we try to compile that, we'll get this error message:

    [0003.77] Error: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(11) : Error, ';': Expression has no effect
    
  3. The source of that error is in this line:

    Int1 + Int2;
    

    We're not assigning the result of that to any variable, and we're not using it as a comparison or anything like that. If we wrote it like the following we wouldn't get the error:

    Int1 = Int1 + Int2;
    

    Not something you'd come across often, but it's good to know what the error message means.

  4. You would also get the same error message if a function parameter had the same name as another function, and you tried calling that function as in the following code:

    function bool UsedBy(Pawn Bump)
    {
    Bump(none, none, vect(0,0,0));
    }
    

    In this case we're naming the parameter Bump, which is the same name as a function that already exists for Actor classes. When we try calling Bump the function, it thinks we're trying to typecast the variable, giving us an error.

  5. Variable declarations themselves are pretty simple as long as we remember when to use var and when to use local. There is one special case that you may come across. If we wanted to make an array of class variables, we would want to write it like the following:

    var array<class<Projectile>> ProjectileClasses;
    

    This is different than having an array of actual Projectile actors, here we're just specifying class names.

  6. If we compiled that code, we would get this error:

    [0003.87] Log: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(4) : Error, Missing '>' in 'class limitor'
    
  7. The reason that error comes up is that the compiler thinks that the two greater-than signs ( >> ) are bitwise or vector rotating operator. To fix this, we would put a space in between them:

    var array<class<Projectile> > ProjectileClasses;
    

What just happened?

There aren't many instances where variable declaration and its use would give you compiler errors, as long as you're careful with the names and typecasting.

This is by no means is an exhaustive list of compiler errors you may encounter when working with UnrealScript. Most of them will be self-explanatory, and the error will always have a line number associated with it:

[0003.77] Error: R:UDKUDK-AwesomeGameDevelopmentSrcBrokenGameClassesBrokenActor.uc(11) : Error, ';': Expression has no effect

The number in parentheses tells us what line to look at in the class, in this case 11. Keep in mind that although the compiler encountered the error on that line, the actual error may not be on that line. Missing brackets or parentheses often cause the compiler to give a line number that is after the actual error.

Just remember, when you get a compiler error, don't panic! Read what the compiler is telling you, and carefully examine your code. Most compiler errors are very simple to fix. Are you trying to use a variable or function that hasn't been declared or doesn't exist for the class you're working in? Are all your brackets, parentheses, and semicolons in place? With experience you will quickly be able to find and fix any errors that the compiler is giving you.

Debugging

Just because code compiles, however, doesn't mean it's going to work. There are a lot of things that can go wrong that the compiler won't complain about, but will break your game nonetheless. In this section of the chapter,we're going to talk about some debugging techniques you can use to figure out why the code isn't doing what you want it to do. We'll also keep an eye on the log to catch and fix any errors that happen while the game is running.

Accessed none

By far the most common problem you will run into while debugging your code is the Accessed None. It is also the easiest to avoid, as long as you make no assumptions about Actor variables in your code. Let's take a look.

Time for action Dealing with Accessed None

To test this we need to purposely create an Accessed None, so let's do that now. We'll add a PostBeginPlay function to our BrokenActor.

  1. Let's say we wanted to know the speed of a Projectile. We could add a variable and write a function like this in our PostBeginPlay:

    var Projectile MyProjectile;
    simulated function PostBeginPlay()
    {
    local float ProjectileSpeed;
    ProjectileSpeed = MyProjectile.Speed;
    `log("ProjectileSpeed:" @ ProjectileSpeed);
    }
    
  2. Compile the code and run the game.

  3. Exit the game and check the log:

    [0004.99] ScriptWarning: Accessed None 'P'
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:PostBeginPlay:004F
    [0004.99] ScriptLog: ProjectileSpeed: 0.0000
    
  4. We're getting an Accessed None warning and the speed is logging as 0. That doesn't seem right, especially when the default for the Projectile class is 2000. Let's add another log to see what's going on:

    var Projectile MyProjectile;
    simulated function PostBeginPlay()
    {
    local float ProjectileSpeed;
    `log("Projectile:" @ P);
    ProjectileSpeed = P.Speed;
    `log("ProjectileSpeed:" @ ProjectileSpeed);
    }
    
  5. Compile the code and run the game.

  6. Exit the game and check the log again:

    [0005.03] ScriptLog: Projectile: None
    [0005.03] ScriptWarning: Accessed None 'P'
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:PostBeginPlay:004F
    [0005.03] ScriptLog: ProjectileSpeed: 0.0000
    

What just happened?

Accessed None warnings happen when you try to access variables or functions in an Actor variable that isn't referencing an actor, in other words:

SomeActorVariable = none

This could happen if you never assign a reference to an Actor variable, as happened here. We declared a variable of the type Projectile, but we never assigned it a value. By default Actor variables are None, so we're trying to access a variable in an actor that doesn't exist, which gives us the error.

Let's see if we can fix this by assigning a value to it.

Time for action Fixing an Accessed None

Our Projectile variable doesn't have anything assigned to it, so let's try to fix that. We'll use the foreach iterator to find one and assign it to our MyProjectile variable.

  1. Let's add the lines to our PostBeginPlay function:

    var Projectile MyProjectile;
    simulated function PostBeginPlay()
    {
    local float ProjectileSpeed;
    foreach DynamicActors(class'Projectile', MyProjectile)
    break;
    `log("Projectile:" @ MyProjectile);
    ProjectileSpeed = MyProjectile.Speed;
    `log("ProjectileSpeed:" @ ProjectileSpeed);
    }
    
  2. Now the code will search for a Projectile and assign it to our MyProjectile variable.

  3. Compile the code and run the game.

  4. Exit the game and take a look at the log:

    [0005.14] ScriptLog: Projectile: None
    [0005.14] ScriptWarning: Accessed None 'MyProjectile'
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:PostBeginPlay:004F
    [0005.14] ScriptLog: ProjectileSpeed: 0.0000
    
  5. Well that didn't work, and if we think about it it's obvious why. PostBeginPlay is run as soon as the game starts, so there aren't going to be any Projectile actors on the map.

  6. In addition to the warning, when we try to access any variables or functions, we can also get another error if we try to assign any values to variables in a non-existent actor. If we changed our function to this, for instance:

    var Projectile MyProjectile;
    simulated function PostBeginPlay()
    {
    foreach DynamicActors(class'Projectile', MyProjectile)
    break;
    MyProjectile.Speed = 1000.0;
    }
    
  7. Instead of just accessing the Speed variable, we're trying to assign a value here. Let's compile the code and run the game.

  8. Exit the game and take a look at the log:

    [0005.15] ScriptWarning: Accessed None 'MyProjectile'
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:PostBeginPlay:0029
    [0005.16] ScriptWarning: Attempt to assign variable through None
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:PostBeginPlay:003D
    
  9. That second line is new, and is caused by the following line:

    MyProjectile.Speed = 1000.0;
    

    The game is letting us know that we're trying to assign a value to a variable in an actor that doesn't exist.

  10. These warnings are helpful, but how do we avoid Accessed None warnings? Let's use a conditional statement to check if our actor variable has anything assigned to it before we try to use it:

    var Projectile MyProjectile;
    simulated function PostBeginPlay()
    {
    foreach DynamicActors(class'Projectile', MyProjectile)
    break;
    if(MyProjectile != none)
    MyProjectile.Speed = 1000.0;
    else
    `log("MyProjectile == none, not doing anything.");
    }
    
  11. Compile the code and run the game.

  12. Exit and take a look at the log:

    [0005.06] ScriptLog: MyProjectile == none, not doing anything.
    

What just happened?

Using conditional statements to check if an actor has a variable will prevent you from getting an Accessed None warning, and can help you do different actions based on whether or not it has a value. For instance, if we wanted to have the player do a different action based on whether they were holding a weapon or not, we might use the StartFire function in our PlayerController like this:

exec function StartFire( optional byte FireModeNum )
{
if(Pawn.Weapon != none)
FireWeapon();
else
DoSomeOtherAction(); }

The important thing is, before you try to access any variables or functions in an actor, ALWAYS check to see if it exists before you do. This also applies to function parameters, as we'll see next.

Time for action Accessed None in function parameters

Even when we're using functions, we have to be careful about Accessed None warnings. Just because a function is passing an actor in doesn't mean it's a valid reference.

  1. Let's remove our MyProjectile variable and rewrite the PostBeginPlay function:

    simulated function PostBeginPlay()
    {
    AdjustProjectile(none);
    }
    
  2. Now let's write the custom AdjustProjectile function:

    function AdjustProjectile(Projectile MyProj)
    {
    `log("MyProj:" @ MyProj);
    MyProj.Speed = 1000.0;
    }
    
  3. Compile the code and run the game.

  4. Exit and take a look at the log. We're getting the same warnings as before:

    [0005.09] ScriptWarning: Accessed None 'MyProj'
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:AdjustProjectile:0024
    [0005.09] ScriptWarning: Attempt to assign variable through None
    BrokenActor BrokenMap.TheWorld:PersistentLevel.BrokenActor_0
    Function BrokenGame.BrokenActor:AdjustProjectile:0038
    

What just happened?

In PostBeginPlay we're calling AdjustProjectile with none. It may not seem logical but it's perfectly valid. To see how something like this could happen with actual code, let's take a look at the Died function in Pawn:

function bool Died(Controller Killer, class<DamageType> DamageType, vector HitLocation)

Useful information is used in the preceding function, like Killer. That variable is passed along to the GameInfo so it can give our killer a score for killing us, among other things. But what if there was no Killer? It can happen sometimes, for instance when we change teams our Pawn is killed, but no one was the Killer:

function PlayerChangedTeam()
{
Died( None, class'DamageType', Location );
}

When writing and using variables, even as function parameters, we have to write our code knowing that they might not always have a value. ALWAYS check that your actor variables are not None before trying to use them.

Using the log

More than anything else, the log is your main tool to figure out why your code isn't working. Using it to log values of variables, or at the beginning of functions to let you know they're being called, even just throwing one in PostBeginPlay to make sure your actor classes are being used at all, the log is an incredibly useful debugging tool. Let's take a look at a problem and see if we can figure out what's going wrong using the log.

Time for action Setting up a scenario

Before we start debugging, we need something that's broken that we can use to test our skills. The first thing we need to do is get our own PlayerController class working.

  1. Remembering that our PlayerController class is specified in the GameInfo, that is where we need to start. Let's add a new file to our DevelopmentSrcBrokenGameClasses folder called BrokenGame.uc.

  2. Write the following code into BrokenGame.uc:

    class BrokenGame extends UTDeathmatch;
    defaultproperties
    {
    PlayerControllerClass=class'BrokenGame.BrokenPlayerController'
    bDelayedStart=false
    bUseClassicHUD=true
    }
    
  3. Now we need to create the BrokenPlayerController class. Start by creating a new file in DevelopmentSrcBrokenGameClasses called BrokenPlayerController.uc.

  4. Write the following code in BrokenPlayerController.uc:

    class BrokenPlayerController extends UTPlayerController;
    defaultproperties
    {
    }
    
  5. Now we're going to change BrokenActor a bit. We'll borrow some code from the AwesomeEnemy class to turn it into a kind of pet that will follow us around. Open BrokenActor.uc and type the following code into it:

    class BrokenActor extends Actor
    placeable;
    var Pawn Master;
    var float MovementSpeed;
    var float StopDistance;
    simulated function PostBeginPlay()
    {
    foreach DynamicActors(class'Pawn', Master)
    break;
    }
    auto state Following
    {
    simulated function Tick(float DeltaTime)
    {
    local vector NewLocation;
    if(Master != none && VSize(Location - Master.Location) > StopDistance)
    {
    NewLocation = Location;
    NewLocation += normal(Master.Location - Location) * MovementSpeed * DeltaTime;
    SetLocation(NewLocation);
    }
    }
    }
    defaultproperties
    {
    MovementSpeed=256.0
    StopDistance=128.0
    bBlockActors=true
    bCollideActors=true
    Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
    bEnabled=true
    End Object
    Components.Add(MyLightEnvironment)
    Begin Object Class=StaticMeshComponent Name=PetMesh
    StaticMesh=StaticMesh'UN_SimpleMeshes.TexPropCube_Dup'
    Materials(0)=Material'EditorMaterials.WidgetMaterial_Y'
    LightEnvironment=MyLightEnvironment
    Scale3D=(X=0.125,Y=0.125,Z=0.25)
    End Object
    Components.Add(PetMesh)
    Begin Object Class=CylinderComponent Name=CollisionCylinder
    CollisionRadius=32.0
    CollisionHeight=64.0
    BlockNonZeroExtent=true
    BlockZeroExtent=true
    BlockActors=true
    CollideActors=true
    End Object
    CollisionComponent=CollisionCylinder
    Components.Add(CollisionCylinder)
    }
    
  6. Compile the code.

  7. Before we test the code, let's open BrokenMap in the editor and make sure a BrokenActor is still placed somewhere near the player start. Once that's done save the map and close the editor.

  8. For the final step, we have to change our batch file to use our BrokenGame so our BrokenPlayerController will be used. Change Broken Map.bat to this:

    C:UDKUDK-AwesomeGameBinariesWin32UDK.exe BrokenMap?GoalScore=0?TimeLimit=0?Game=BrokenGame.BrokenGame -log
    
  9. Save and close the batch file.

  10. Run the game with the changed batch file.

What just happened?

Our pet doesn't seem to be following us. What's going on? Let's see if we can use the log to find out why it's not working and fix the problem.

Time for action Debugging using the log

The first question we need to ask is: are our classes being used in the first place? Let's put a few logs in to find out.

  1. We'll start with BrokenActor. Even though we see it in the map when we run the game, we shouldn't make any assumptions about what's going on. We'll add a log in PostBeginPlay:

    simulated function PostBeginPlay()
    {
    `log("BrokenActor PostBeginPlay!");
    foreach DynamicActors(class'Pawn', Master)
    break;
    }
    
  2. We'll also add one in BrokenGame:

    function PostBeginPlay()
    {
    `log("BrokenGame PostBeginPlay!");
    }
    
  3. And finally one in BrokenPlayerController:

    simulated function PostBeginPlay()
    {
    `log("BrokenPlayerController PostBeginPlay!");
    }
    
  4. Compile the code and run the game.

  5. Exit and take a look at the log. We'll see that all three actors are logging their PostBeginPlay functions:

    [0005.44] ScriptLog: BrokenActor PostBeginPlay!
    [0005.44] ScriptLog: BrokenGame PostBeginPlay!
    [0005.44] Log: Bringing up level for play took: 0.032277
    [0005.44] ScriptLog: BrokenPlayerController PostBeginPlay!
    
  6. Now that we know our actors are indeed being used, we need to debug further. We know the movement code for our BrokenActor takes place in its Following state's Tick function, so let's add some logs there:

    simulated function Tick(float DeltaTime)
    {
    local vector NewLocation;
    `log("BrokenActor Tick!");
    if(Master != none && VSize(Location - Master.Location) > StopDistance)
    {
    `log("BrokenActor Calculating new location!");
    NewLocation = Location;
    NewLocation += normal(Master.Location - Location) * MovementSpeed * DeltaTime;
    SetLocation(NewLocation);
    }
    }
    
  7. Let's compile the code and see what happens when we run the game.

  8. Exit the game and take a look at the log:

    [0005.88] ScriptLog: BrokenActor Tick!
    [0005.92] ScriptLog: BrokenActor Tick!
    [0005.94] ScriptLog: BrokenActor Tick!
    [0005.95] ScriptLog: BrokenActor Tick!
    
  9. The code doesn't seem to be getting into our if statement. Let's see if we can figure out why. Let's log the conditions to see which one is failing. We'll start with Master:

    simulated function Tick(float DeltaTime)
    {
    local vector NewLocation;
    `log(Master);
    if(Master != none && VSize(Location - Master.Location) > StopDistance)
    {
    `log("BrokenActor Calculating new location!");
    NewLocation = Location;
    NewLocation += normal(Master.Location - Location) * MovementSpeed * DeltaTime;
    SetLocation(NewLocation);
    }
    }
    
  10. Compile the code and run the game.

  11. Exit and take a look at the log:

    [0005.68] ScriptLog: None
    [0005.73] ScriptLog: None
    [0005.75] ScriptLog: None
    
  12. Well that would explain it. Master is never getting set. Let's see why. If we take a look a bit earlier in the log we'll see our PostBeginPlays:

    [0005.35] ScriptLog: BrokenActor PostBeginPlay!
    [0005.35] ScriptLog: BrokenGame PostBeginPlay!
    [0005.36] Log: Bringing up level for play took: 0.035747
    [0005.36] ScriptLog: BrokenPlayerController PostBeginPlay!
    
  13. It looks like the BrokenActor is being initialized first, so we're not going to be able to get our Master there. Let's try checking it in Tick and assigning it if it doesn't exist. First we'll rename PostBeginPlay to GetMaster:

    function GetMaster()
    {
    `log("BrokenActor GetMaster!");
    foreach DynamicActors(class'Pawn', Master)
    break;
    }
    
  14. Now we'll add the function call in Tick:

    simulated function Tick(float DeltaTime)
    {
    local vector NewLocation;
    if(Master == none)
    GetMaster();
    if(Master != none && VSize(Master.Location - Location) > StopDistance)
    {
    `log("BrokenActor Calculating new location!");
    NewLocation = Location;
    NewLocation += normal(Master.Location - Location) * MovementSpeed * DeltaTime;
    SetLocation(NewLocation);
    }
    }
    
  15. Now let's compile the code and check if it works.

  16. Run the game and we'll see that the BrokenActor is now following us around! And if we check the log we'll see we're finally getting into the if statement:

    [0007.66] ScriptLog: BrokenActor Calculating new location!
    [0007.67] ScriptLog: BrokenActor Calculating new location!
    [0007.69] ScriptLog: BrokenActor Calculating new location!
    
  17. Close the game and remove all of the log lines from our classes.

What just happened?

This was a simple example of debugging, but we can see how using the log can help us figure out where our code is going wrong and give us clues to what we need to do to fix it. Knowing how we think the code should act, we can put logs in places where it's breaking and follow the chain of events written to the log file. This helps us narrow it down to the specific line, function, or variable that's causing the issue.

Sometimes we'll want to debug a class that has several instances running at the same time. With all of them logging it can be difficult to figure out what's going on. Ideally we would only want one of the Actors in the level, but if that's not feasible there are ways around it. One method we can use to filter down the log is to use the Actor's name variable in an if statement. That way only one of the Actors will write to the log. If the Actors are placed in the level, selecting one will show the Actor's name at the bottom of the editor:

What just happened?

We can use that in an if statement like this:

if(Name == 'BrokenActor_0')
`log(Master);

This also works for Actors that are spawned during gameplay. The first instance will start with 0 as its suffix, and increase by 1 with every instance spawned, so BrokenActor_0, BrokenActor_1, BrokenActor_2, and so on. This would be helpful if we were trying to debug the AwesomeEnemy class from the previous chapters, for instance.

Next we're going to discuss a few ways we can optimize our code for performance, and a few things to avoid when writing code to keep it running fast.

Optimization

We've gotten through the compiler errors. We've fixed all of the Accessed None warnings. We've used the log to debug our broken code. What else can go wrong? Well, if we're using inefficient code we can start to take hits to our game's frame rate as well. There are a few things we need to avoid doing, as well as a few tools to help us keep our code running quickly. The most important of these is the profiler.

The profiler

Something we might not think about when we start programming is how fast our code is running, and which classes or functions are taking the most time to run. So how do we find out? This is where the profiler comes in handy. It can give us an organized view of exactly where UnrealScript is spending its time. Let's take a look at it.

Time for action Using the profiler

To use the profiler we don't need to do anything in UnrealScript itself; we'll use a built-in function of the Unreal engine for it.

  1. Double-click on the batch file to run BrokenMap.

  2. Hit the tilde key ( ~ ) to bring up the console.

  3. Type profilegame start into the console and hit Enter. We'll see this message on screen:

    PROFILING WITH AI LOGGING ON!
    

    As well as this message in the log:

    [0009.46] Log: GameplayProfiler STARTING capture.
    
  4. Run around for a little bit to give the profiler time to collect information. Around ten seconds is good.

  5. When you're ready, hit tilde (~) to bring up the console again, and type profilegame stop and hit Enter. The message on screen will go away and we'll see this message in the log:

    [0036.62] Log: GameplayProfiler STOPPING capture.
    
  6. Exit the game.

  7. If we look in the UDKGame folder, we'll see a new folder has appeared called Profiling. If we look in that folder, we'll see a file with the time that we ran the profiler in its file name: UDK-2011.09.22-14.46.15.gprof

  8. To open this file, go into the UDK-AwesomeGameBinaries folder and run GameplayProfiler.exe. We'll see the program start up looking like this:

    Time for action Using the profiler
  9. Press Open File, and navigate to the UDKGameProfiling folder and select the .gprof file:

    Time for action Using the profiler
  10. The top panel of the profiler shows us the frame by frame performance of our game. If there are any spikes you know that those are the areas to focus on. Clicking anywhere on this graph will set the profiler to that frame. If your graph extends out that far, click somewhere near 200 so we can take a look at the performance a little bit into the game.

  11. The bottom-left panel of the profiler will show us the time taken for scripts to run, broken down by actor. If we expand our BrokenActor we can see the performance of it:

    Time for action Using the profiler
  12. For our BrokenActor, calculating the dynamic lighting on it is taking the most time, followed by our Tick function with the movement calculations. Overall not a lot of time. What would we see if we really screwed things up?

  13. Close the profiler for now and open BrokenActor in ConTEXT.

  14. What if we rewrote our Tick function to run the search for Master every time?

    simulated function Tick(float DeltaTime)
    {
    local vector NewLocation;
    foreach AllActors(class'Pawn', Master)
    break;
    if(Master != none && VSize(Location - Master.Location) > StopDistance)
    {
    NewLocation = Location;
    NewLocation += normal(Master.Location - Location) * MovementSpeed * DeltaTime;
    SetLocation(NewLocation);
    }
    }
    
  15. If we compiled that code and ran the game, then recorded with the profiler for a few seconds, we might see something like this when we took a look at the .gprof file:

    Time for action Using the profiler
  16. We can see that our Script Time has increased. It might seem like a small increase, but with enough BrokenActors on the map a small increase like this could really impact performance.

What just happened?

With our relatively simple game the profiler might seem useless. There isn't a whole lot going on, obviously. But as an example, take a look at a typical profile from a normal UDK Deathmatch with a few bots running around:

What just happened?

With a profile like this we can see how it can become useful. Spikes like that in the graph can easily start to impact performance, making the game stutter in places. As you're developing a game it's a good idea to run the profiler every once in a while even if your performance seems fine. It will help you catch problems before they start hurting your game's performance.

Clock / UnClock

The profiler is useful for seeing the overall picture of your game's performance, but what if you just want to know how fast a certain piece of code is executing? You might want to see if a certain way of writing an iterator is faster than another, for instance, or you might just want to go further than the profiler and figure out which part of a slow running function is impacting your game's performance. An easy way to do this is to use the Clock and UnClock functions defined in Actor.uc.

Time for action Using Clock and UnClock

Let's run a little test with BrokenActor to see how to use these two functions.

  1. Open BrokenActor.uc in ConTEXT.

  2. We'll use PostBeginPlay for this. Let's write it like this:

    function PostBeginPlay()
    {
    local int i;
    local float StopWatch, Size;
    local vector A, B;
    Clock(StopWatch);
    for(i=0; i<1000; i++)
    {
    A = VRand() * 1000;
    B = VRand() * 1000;
    Size = VSize(A - B);
    }
    UnClock(StopWatch);
    `log("Time taken to execute:" @ StopWatch);
    }
    
  3. What we're doing here is running a loop 1000 times. Each time we take two vectors, A and B, and randomize them. Then we calculate the distance between them with VSize. We use a float called StopWatch in the Clock and UnClock functions, then log the value so we can see how long it took to run the loop.

  4. Compile the code. We'll get a warning about Size, but we'll ignore it for this test.

  5. Run the game, then exit and take a look at the log:

    [0005.32] ScriptLog: Time taken to execute: 0.6553
    

What just happened?

The value used by Clock and UnClock is given in milliseconds, so running that loop took a little over half a millisecond. It might not seem like a lot of time, but if we remember that for a game running 60 frames per second, each frame is taking 16.667 milliseconds, it can add up quick especially if there are a lot of these actors on the map.

Best practices

When creating a game you absolutely must keep performance in mind when writing your scripts. Even though most of your game's performance will be focused on the art side of things with polygon counts and texture usage, unoptimized scripts can have a bad effect on your game. Here are a few things to look out for:

  • Avoid using too much code in Tick:

    Since Tick is run every frame, any code inside it must be carefully considered. Avoid iterators like foreach and other slow functions like Trace unless they're absolutely necessary. When possible, store the results of a foreach in an array, which can be iterated through faster. Also consider using a repeating Timer instead of Tick if your code needs to run often but doesn't necessarily need to run every frame.

  • Let the engine handle it:

    Collision, movement, physics... some things are best left to the engine to handle. Native engine code runs faster than UnrealScript, so when possible use functions that are already provided, instead of writing your own. For example, it would be easy to calculate gravity for an actor and set its velocity yourself, but letting the engine deal with the physics lets you avoid unnecessary performance hits from it.

  • Create and destroy actors only when necessary:

    This is doubly important for online games, where replication of new actors takes up valuable bandwidth. As an example, it would be horribly inefficient to create a gun that ejected shell casings in the form of actual actors when a particle effect would suffice.

  • Optimize your variable usage:

    In addition to making your code easier to read, keeping variable usage to a minimum saves memory and calculations. If you're constantly calculating a local variable's value for use in equations or as an actor reference, consider if it should be moved to an instance variable for that class. For example, the EyeHeight variable in the Pawn class is used in a few different functions, but only one function changes its value.

  • Optimize your conditional statements:

    When writing conditional statements such as if, write them in a way that will exit the statement as soon as possible. For example, if you wanted to check if a Pawn's health was less than 25/100 and it had a weapon, which would be the least likely? For the most part you're always going to have a weapon, so writing it like the follwoing would exit the if statement sooner:

    if(Health < 25 && Weapon != none)
    DoSomething();
    

    Most of the time your Pawn's Health will be greater than 25, so the if statement will read the first part of your statement and exit immediately. If it were written the other way around, most of the time the if statement would check the Weapon, see that it exists, and then continue to the second part where it would exit. Writing it as above will save the execution of one of the statements.

    The opposite is true when using OR instead of AND. You would want to examine the most likely thing first.

    The exception to this is when you need to write the conditional statement to avoid Accessed None warnings. Speaking of which...

  • Fix log errors as you find them:

    Don't let Accessed None warnings and other errors accumulate in your log. The log is there to help you, so fix any errors that show up from your code! Also, don't forget to remove any debugging code you have added. Writing to the log creates a small but noticeable performance hit, especially when used in a function like Tick.

Pop quiz Errors and conditions

  1. What does this compiler error mean?

    Error, Unexpected end of file at end of Class
    
  2. If this line were giving us an Accessed None, how would we fix it?

    if(SomeActor.SomeVariable > 8)
    
  3. When writing an AND conditional statement, which check should go first?

    Most likely to return true.

    Most likely to return false.

Summary

We learned a lot in this chapter about debugging and optimizing our code.

Specifically, we covered:

  • Common compiler errors, what they mean, and how to fix them

  • How to avoid Accessed None warnings

  • How to use the log to debug code

  • How to use the Profiler and Clock/UnClock

Now that we've learned about code optimization, we can cover a few other random topics to finish our course in UnrealScript!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.100.180