UT3 Development Notes
From Gneu.org
Below you will find a not quite so comprehensive listing of the notes i have accumulated for developing in UT3. It is bound to update as time goes on and may eventually spur off discussion or tutorials, and be dropped or moved to other pages. I will do my best to keep a listing of such topics at the end of this document.
Contents |
Overall Suggestions
- NEVER touch the game install directory.
- When developing, and even right up to deployment, keep the code separate from the rest of the package. Every time you build the code you overwrite the package and will have to rebuild it.
- KISS (Keep It Simple Stupid)
- Don’t forget to update your UTEditor.ini with the new packages
- Be liberal with your logging
- `log is an alias for LogInternal() - btw thats a backtick, not an apostrophe.
- Use $ and @ to concatenate strings together
- Do as much work in the ini and configuration files as is possible to avoid compiling.
- UScript is Strongly typed
- 1/2 == 0;
- 1/2.0f = .5;
-
SetTimer(f_TimeTillCall, b_Loop, s_FunctionName);
- There are four types of Client Messages - Say, TeamSay, Event, CriticalEvent
Useful Logs
- `log( "Something" @ WorldInfo.NetMode, , 'LinePrefix' );
- LinePrefix: Something (NM_Client|NM_DedicatedServer|NM_StandAlone)
- `log( "Something", 1 > 0, 'LinePrefix');
- This is only printed if 1 > 0
Mutators
- Do not subclass Pawn for replication
- Begin by rewriting other mutators that already work; study how they work and you will feel better as a human being
- DefaultProperties are not always necessary
- When you write a mutator think about what mutators it wouldn’t work well with, such as for instance a quad and a dodeca jump mutator, and use the groups to eliminate race conditions.
- You use the | to include multiple groups
- Mutators have a function called Mutate that accepts the rest of the string as a single argument and allows you to trigger events or modify values on the fly in mutators.
- ModifyPlayer is executed on every Pawn as it is spawned in the game.
Replication
- You cannot replicate for another class
- If you are modifying a variable on the server with a mutator, confirm that it is already replicated to the client, otherwise you are going to spend a lot of time spinning your wheels before you remember to use a player info object to get it to work properly.
- No mutators are able to execute simulated functions, as the mutator base class has none and it only works when propagated to children.
- In order for a variable to be on the client that is changed on the server, you should use repnotify and a replication block (such as the one that follows)
- It may also be good to include the bNetDirty and bNetInitial tests in such cases.
| Server Priority | Client Priority |
|---|---|
replication
{
if( Role == ROLE_Authority )
iMaxNumJumps, iMaxJumpBoost;
}
|
replication
{
if( Role < ROLE_Authority )
iMaxNumJumps, iMaxJumpBoost;
}
|
Info Classes
- Tick is the first event to be executed that has access to the owner.
| Attaching the info class to a client |
|---|
function NotifyLogin(Controller NewPlayer)
{
local Info_JumpMod A;
if( UTPlayerController( NewPlayer ) != None )
{
A = Spawn( Class'Info_JumpMod', NewPlayer );
A.riMaxNumJumps = iMaxNumJumps;
A.riMaxJumpBoost = iMaxJumpBoost;
}
Super.NotifyLogin(NewPlayer);
}
|
| Using the ReplicatedEvent to keep track of variable transmission and update the client. |
|---|
var repnotify int riMaxNumJumps;
var repnotify int riMaxJumpBoost;
var repnotify UTPawn rPlayerPawn;
Replication
{
if(bNetDirty && (Role == ROLE_Authority))
riMaxNumJumps, riMaxJumpBoost, rPlayerPawn;
}
simulated event ReplicatedEvent(name VarName)
{
if (rPlayerPawn != None)
{
rPlayerPawn.MaxMultiJump = riMaxNumJumps;
rPlayerPawn.MultiJumpBoost = (riMaxJumpBoost * 40);
rPlayerPawn.MultiJumpRemaining = riMaxNumJumps;
}
Super.ReplicatedEvent(VarName);
}
|
UIScenes
UT3 takes a new approach to GUI development. It is now necessary to add a UIScene to your mutators in order to provide things like the CDKey entry page, Preferences and Mutator Configuration menus.
- When you are setting a default value for the mutator make sure you call UpdateCaption() for each control that has a default value. Otherwise, the caption for the control will be left at its default value.
- Try to keep things straightforward when formatting. You are bound to have to do it again.
- Many of the classes herein are either incomplete or not intuitive.
States
- Begin blocks have no way of chaining up to their parent state.
class SuperWeapon extends Weapon;
state WeaponFiring extends Active
{
Begin:
`DebugMessage( "Hello World from WeaponFiring within SuperWeapon" );
}
class SuperDUPERWeapon extends SuperWeapon;
state WeaponFiring
{
Begin:
`DebugMessage( "Hello World from WeaponFiring within SuperDUPERWeapon" );
}
- SuperWeapon's version of the Begin block will NOT and has NO way of being called (unless we discover otherwise later).
- note that SuperDUPERWeapon's WeaponFiring state does NOT need to re-extend from the base state.
- In derived classes, overriding a base class state that extends another state does NOT require you to re-extend the base state (see above example).
- Latent functions such as Sleep may only be called in begin blocks.
Finding the local player
There is a class titled LocalPlayer. Additionally, line 1690 of Actor.uc:
/** iterator LocalPlayerControllers() returns all locally rendered/controlled player controllers (typically 1 per client, unless split screen) */ native final iterator function LocalPlayerControllers(class<PlayerController> BaseClass, out PlayerController PC);
Using it will return all local players. Go me.
