UE3 programming pitfalls

Some small bits of advice and common issues we've noticed.

Objects referencing actors / "World cleanup..."

In UE3, everything that can be placed or spawned in a level is an Actor. When the current level is changed, any Actors within the level are forcibly garbage collected.

Sometimes this will fail, and the game will crash with a world cleanup error. This generally indicates that some non-Actor Object is still referencing an Actor within the world. The Object in question is not being cleaned up, and because it still holds a reference to the World, the World can't be cleaned up either. The error message should include a reference hierarchy that may help in diagnosing the problem.

Uninitialized variables

Most unrealscript variables are totally cool if you use them before they're assigned a value (though you'll get a big yellow Warning when you compile scripts, and it's really just a bad idea). There are cases where uninitialized Strings and uninitialized or empty Dynamic Arrays will crash the game, though. The String issues can be difficult to diagnose, but generally Dynamic Array problems will result in a crash inside a native array iterator function (* we may have successfully cleared these issues; bad dynamic arrays should not be able to crash the game through UnrealScript now)

Assuming that input coming from an RPC is safe

Any variables or functions that are replicated to the server must have their inputs checked, unless they're completely harmless. It takes very little effort for an enterprising individual to send whatever information is desired into either.

This doesn't check inputs:

//This is bad; the server function doesn't check to see if the player is actually an admin.
//A hacker could easily call the server function
 
exec function AdminDoSomething()
{ 
	if(PlayerReplicationInfo.bAdmin)
	{ 
		ServerAdminDoSomething();
	}
}
reliable server ServerAdminDoSomething()
{
	KillEveryone();
}

This one does:

//This is good. We do a check on the server to make sure the input actually makes sense.

exec function AdminDoSomething()
{ 
	ServerAdminDoSomething();
}
reliable server ServerAdminDoSomething()
{
	if(PlayerReplicationInfo.bAdmin)
	{ 
		KillEveryone();
	}
}

Replication is expensive

Don't replicate things that don't need to be replicated. e.g. We were briefly replicating foot steps at one point long before release. There's no reason every client needs to hear exactly the same footsteps. We moved the footstep sound cue into a simulated function that is triggered off the animation playing on a client (instead of being triggered by the animation on the server, and then being replicated to the clients); a far better approach.

Only use the reliable keyword when necessary.

Don't set the bNetDirty flag unless absolutely necessary.

Reduce the net update rate for things that don't need to be updated frequently.

Replication costs in a few ways:

  • The act of checking to see if something needs to be replicated costs CPU cycles
    • This is even worse if we do a visibility check on that something; lots of line checks add up
  • The act of actually replicating costs CPU cycles, and naturally network throughput
  • The functions that are called on the other end, and the repnotifies that are triggered by replicated variables, can cost further CPU cycles

The client keyword calls the function on the owning client

If you use the client keyword in a function, the function gets called on the owning client. It doesn't get called on all clients. We only use this in Weapon, Pawn, and PlayerController-derived classes because ownership is generally meaningless for others. If you want to call something on all clients, you likely want to replicate a variable and then use a repnotify and Replication block (see AOCPawn for examples).

State functions without a Global equivalent

There are situations where the compiler will do unpredictable things with state functions that don't override a global (non-state) equivalent. This is why you'll occasionally see stub global functions immediately preceding a state containing functions of the same signature.

States do not work in non-Actor-derived Objects

They just don't. The compiler won't complain, though.

Optional string function parameters

Cthulhu contributed this:

If you have an optional string parameter in a function signature with a default value, then try to call the function using a string that's longer than that parameter, the game will crash as the engine doesn't seem to allocate sufficient space for the string.

function MyMethod(optional string ExtraPar = "ABCD");
 
....
 
function CrashTheGame()
{
	//this will crash with a buffer overlow; "ABCDEF" is longer than "ABCD"
	MyMethod("ABCDEF");
}
function EverythingIsOkay()
{
	//this won't crash; "ABC" is shorter than "ABCD"
	MyMethod("ABC");

}

If you need a default string value for an optional string parameter, the ideal solution then would seem to be:

function MyBetterMethod(optional string ExtraPar)
{
	if(ExtraPar == "")
	{
		ExtraPar = "ABCD";
	}
}