UDN
Search public documentation:

CodingStandard
日本語訳
中国翻译
한국어

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 > Coding Standards

Coding Standards


Overview


At Epic, we have a few simple coding standards and conventions. Most of these examples are in reference to Unreal Script, but they should apply to C++ coding as well. This document is not meant to be a discussion or work in progress, but rather, reflects the state of Epic's current coding standards. It is provided as a convenience for licensees. Note that the engine does not fully reflect this standard; we have a few hundred thousand lines of legacy code, that we're updating as we add new code or refactor old code.

Code conventions are important to programmers for a number of reasons:

  • 80% of the lifetime cost of a piece of software goes to maintenance.
  • Hardly any software is maintained for its whole life by the original author.
  • Code conventions improve the readability of the software, allowing engineers to understand new code more quickly and thoroughly. We'll certainly be hiring new engineers and interns over the life of this project, and we'll likely be using engine modifications we make new on our next few projects.
  • If we decide to expose source code to mod community developers, we want it to be easily understood.
  • Many of these conventions are actually required for cross-compiler compatibility.

Class Organization : class names are nouns


  • Class comment block
  • Class declaration
  • Variable declarations
  • C++ only: Static and const variables
  • Public variables
  • Protected variables
  • Private variables
  • cpptext
  • Constructors and Destructors
  • Includes BeginPlay( ), Destroyed( ), etc.
  • Methods and states grouped by functionality
  • State-less utility methods (unassoicated with any state) grouped by functionality
  • State-less methods associated with entering or leaving a state should go just before the state.
  • State-associated methods and state for the auto state, if any, should be the first state defined
  • defaultproperties

State Organization : state names are adjectives


  • State comment block (similar to method comments)
  • Methods
  • Begin: code

Method Organization : method names are verbs


  • Method comment block
  • Local variables

Variables: use the proper types in C++!


  • UBOOL for boolean values (4 bytes). BOOL will not compile.
  • TCHAR for a character (NEVER assume the size of TCHAR)
  • BYTE for unsigned bytes (1 byte)
  • SBYTE for signed bytes (1 byte)
  • WORD for unsigned "shorts" (2 bytes)
  • SWORD for signed "shorts" (2 bytes)
  • UINT for unsigned ints (4 bytes)
  • INT for signed ints (4 bytes)
  • QWORD for unsigned "quad words" (8 bytes)
  • SQWORD for signed "quad words" (8 bytes)
  • FLOAT for single precision floating point (4 bytes)
  • DOUBLE for double precision floating point (8 bytes)
  • PTRINT for an integer that may hold a pointer (NEVER assume the size of PTRINT)

Comments


Comments are communication; communication is vital. Some things to keep in mind about comments (from Kernighan & Pike The Practice of Programming):

  • Write self-documenting code:
Bad Good
t = s + l - b; TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;

  • Don't belabor the obvious; either drop the comment or say something useful:
Bad Good
// increment iLeaves // we know there is another tea leaf
Leaves++; Leaves++;

  • Don't comment bad code - rewrite it!
Bad Good
// total number of leaves is sum of  
// small and large leaves less the  
// number of leaves that are both  
t = s + l - b; TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;

  • Don't contradict the code:
Bad Good
// never increment iLeaves! // we know there is another tea leaf
Leaves++; Leaves++;

Our documentation style is based on Javadoc with the exception being C# projects (see the section there). Eventually we'll get around to auto-generated documentation.

The following example demonstrates the format of class, state, method, and variable comments. Remember that comments should augment the code. The code documents the implementation and the comments document the intent. Make sure to update comments when you change the intent of a piece of code.

class Tea extends WarmBeverage
   native, perobjectconfig, transient;
/**
 * This class does lots of neat things.  It works
 * with a few other classes to do other neat things.
 */

/** This stores the value of tea in china. */
var float Price;
  

/**
 * state Brewing
 * This state was created to represent Brewing in the cup.
 * entered: AddBoilingWater is called in the default state.
 * exited:  through Pour or too much time elapses (goes to 
 * GettingBitter)
 */
state Brewing 
{

   /**
    * Steep calculates a delta-taste value for the tea given the 
    * volume and temperature of water used to steep.
    *
    * @param    VolumeOfWater - amount of water used to brew
    *           a positive number of milliliters (not checked!)
    * 
    * @param    TemperatureOfWater - water's temperature in 
    *           degrees Kelvin. 273 < temperatureOfWater < 380
    *
    * @param    NewPotency - the tea's potency after steeping starts;
    *           should be between 0.97 and 1.04
    *
    * @return    returns the change in intensity of the tea in tea taste
    *           units (TTU) per minute
    *
    */
   
   function float Steep (float VolumeOfWater, float TemperatureOfWater, 
                         out float NewPotency)
   {
   }
}

What does a class comment include?

  • A description of the problem this class solves. Why was this class created?
What does a state comment include?
  • A description of the problem this state solves. Why does this object have such a state. Note from the ordering of class stuff above that state associated functions not in a state should appear just before the state comment.
What do all those parts of the function comment mean?
  • Brewing::Steep means that the Steep method is in the Brewing state. This uses C++ scope resolution operator in Unreal Script comments.
  • The purpose of the function is next. This documents the problem this function solves. As has been said above, comments document intent and code documents implementation.
  • inputs lists all of the input parameters to this method. Each parameter should include units of measure, the range of expected values, "impossible" values, and the meaning of status/error codes.
  • inputs/outputs (not included here as it is not needed) documents the parameters that provide values to the function that may be changed by the function. Document as both an input and an output parameter.
  • outputs lists all output only parameters (where the value provided by the caller is ignored). Should have meaning of return values included as well as what happens to the value in case of any error/failure.
  • returns documents the expected return value just as an output variable is documented.

A few special comments. Note there's no space between the slashes and the word; this facilitates searching for these comments without a lot of extraneous hits.
//@debug debug statements to be deleted before release
//@todo still work to be done, follow with description

Note, debug code should generally be left out of the engine. If you must leave it in, it should be wrapped in an if(0) block rather than #if 0, so that it's always compiled, but still optimized out. Include a comment explaining that it is debug code, and include your name and what the debug code was for.

Modifying Third Party Code:

Whenever you modify the code to a library that we use in the engine (wxWindows, FaceFX, Novodex etc), be sure to tag your changes with a //@UE3 comment, as well as an explanation of why you made the change. This make merging the changes into new version of that library easier, and lets licensees easily find any modifications we have made.

Additional Standards for C#


Rather than use the

/** */
formatting of comments for C# use the
///
format which correctly generates code help files. For example:

namespace UnrealLogging
{
   /// <summary>
   /// Indicates the level of the message being logged
   /// </summary>
   public enum LogLevel { LOG_Error, LOG_Warning, LOG_Debug };

   /// <summary>
   /// Generic logging interface. This interface is to be implemented to
   /// provide logging support for whichever the target environment is.
   /// </summary>
   public interface ILogger
   {
      /// <summary>
      /// Writes the message to the implemented logging support
      /// </summary>
      /// <param name="MsgLevel">The severity of the message being written</param>
      /// <param name="StrMsg">The message to write out</param>
      void Log(LogLevel MsgLevel,string StrMsg);

      /// <summary>
      /// Sets the filtering that will be applied to logging. No message
      /// with a level higher than what is set in here will get logged.
      /// </summary>
      /// <param name="MaxLevel">Maximum log level to write out</param>
      void SetFilter(LogLevel MaxLevel);
   }
}

Required Project Settings

Enable the XML documentation file and keep that up to date in Perforce.

Enable the option to treat no code documentation as an error.

Naming Conventions


Variable, function, state, and class names should be clear, unambiguous, and descriptive. The greater the scope of the name, the greater the importance of a good, descriptive name. Avoid over-abbreviation.

All variables should be declared one at time so that a comment on the meaning of the variable can be provided. Also, the JavaDocs style requires it. You can use multi-line or single line comments before a variable, and the blank line is optional for grouping variables.

Variable names should use the following capitalization format: ThisIsAGoodVariableName. Don't use "thisIsABadName" (a.k.a. camel case) or "this_is_a_bad_name" (underscores).

All booleans should ask a true/false question. All functions that return a bool should do the same. All boolean variables must be prefixed with b.

/** the tea weight in kilograms */
var float TeaWeight;

/** the number of tea leaves */
var int TeaNumber;

/** TRUE indicates tea is smelly */
var bool bDoesTeaStink;

/** non-human-readable FName for tea */
var name TeaName;

/** human-readable name of the tea */
var String TeaName; 

Structured types append the name of the class onto the end of the variable:

 
/** Which class of tea to use */
var class<Tea> TeaClass;

/** The sound of pouring tea */
var Sound TeaSound;

/** a picture of tea */
var Texture TeaTexture;

For C++ code, make all class variables private by default. Only make them public if it is required to interact with script, i.e. generated from XxxClasses.h.

A procedure (a function with no return value) should use a strong verb followed by an object. An exception is if the object of the method is the object it is in; then the object is understood from context. Names to avoid include those beginning with "Handle" and "Process"; the verbs are ambiguous.

Tea SomeT;
SteepTea(SomeT);    // method names type of object processed
SomeT.Pour();       // method invoked on Tea; verb is enough

Functions that return a value should describe the return value; the name should make clear what value the function will return. This is particularly important for boolean functions. Consider the following two example methods:

function bool CheckTea(Tea t) {...} // what does TRUE mean?
function bool IsTeaFresh(Tea t) {...} // name makes it clear TRUE means tea is fresh

State and class names should begin with a capital letter and use internal capitalization for readability. Class names should be nouns and state names should indicate states of being (such as adjectives). Note that a state is never used without a class so there is always an implicit object.

Use all the variables you pass in. If you don't use a value, remove it from the parameter list or keep the parameter type but comment out the parameter:

void Update(FLOAT DeltaTime, UBOOL /*bForceUpdate*/);

Unreal Script: parameters passed by reference should use an out_ prefix:

function PassRefs (out float out_wt, float ht, out int out_x)

Const

Whenever you can use const in C++, use const. Particularly on function parameters and class methods. const is documentation as much as it is a compiler directive.

Virtual

When declaring a virtual function in a derived class that overrides a virtual function in the parent class, you must use the virtual keyword. Although it is optional according to the C++ standard because the 'virtual' behaviour is inherited, code is much clearer if all virtual function declarations use the virtual keyword.

Execution Blocks


braces { }

Brace wars are foul. Epic has a long standing usage pattern of putting braces on a new line. Please continue to adhere to that.

if - else

Each block of execution in an if-else statement should be in braces. This is to prevent editing mistakes - when braces aren't used, someone could unwittingly add another line to an if block. The line wouldn't be controlled by the if expression, which would be bad. Worse yet is when conditionally compiled items cause if/else statements to break. So always use braces.

if (bHaveUnrealLicense)
{
   InsertYourGameHere();
}
else
{
   CallMarkRein();
}

A multi-way if statement should be indented with each else if indented the same amount as the first if; this makes the structure clear to a reader:

if (TannicAcid < 10)
{
   log("Low Acid");
}
else if (TannicAcid < 100)
{
   log("Medium Acid");
}
else
{
   log("High Acid");
}

Tabs

Indent code by execution block. Unreal Script class member functions need not be indented, because classes are not enclosed in a block. Use tabs, not spaces, to indent code. That way, everyone can view the code at their preferred tab level.

Switch statements

Except for empty cases (multiple cases having identical code), switch case statements should explicitly label that a case falls through to the next case. Either include a break or a falls-through comment in each case. Other code control-transfer commands (return, continue, etc.) are fine as well.

Always have a default case, and include a break - just in case someone adds a new case after the default.

switch (condition)
{ 
   case 1: 
      --code--;
      // falls through
   case 2:
      --code--;
      break;
   case 3:
      --code--;
      return;
   case 4:
   case 5:
      --code--;
      break;
   default:
      break;
}

defaultproperties

Default properties should be listed in order: first inherited variables, then class variables. Remember, config and localized variables can no longer be defined in defaultproperties blocks in UE3.

defaultproperties
{
   // variables inherited from Object
   bGraphicsHacks=TRUE
   
   // variables inherited from WarmBeverage
   bThirsty=FALSE

   // class variables
   bTeaDrinker=TRUE
}

Boolean expressions


In C++, always use TRUE or FALSE as values in boolean expressions. Do not use true or false, which can cause crazy compiler/definition problems. Don't use 0 or 1 because they're not as clear to read.

In UnrealScript, you should use true and false (lowercase).

Don't be afraid to make up some local bools to make code more readable. In the second example below, the variable names make it very easy to tell under what conditions DoSomething() is called.

if ((Blah.BlahP.WindowExists.Etc && stuff) && 
    !(Player exist && Gamestarted && player still has pawn &&
    IsTuesday())))
{
   DoSomething();
}

should be replaced with

local bool bLegalWindow;
local bool bPlayerDead;

bLegalWindow = (Blah.BlahP.WindowExists.Etc && stuff);
bPlayerDead = (Player exist && Gamestarted && 
               player still has pawn && IsTuesday());

if ( bLegalWindow && !bPlayerDead ) 
{
   DoSomething();
}

Notice: expressions are broken after an operator (the operator ends the previous line) and following lines are aligned inside the appropriate parenthese.

Similarly, there is no code that is "too simple" for factoring out into a function. If you were using the bLegalWindow variable in multiple functions, it would make sense to factor it out into its own function (assuming the parameters are easily available).

General style issues:


  • Avoid multiple returns from a function. Unless it will overly obfuscate your code, use a single return statement. This makes code much easier to maintain, as it's simpler to track the path of execution, make debugging changes, etc.

  • Never pass a structure into a debugf/warnf/appErrorf function. Use * for FNames and FStrings to make them printable with %s.

  • Minimize dependency distance. When code depends on a variable having a certain value, try to set that variable's value right before using it. Initializing a variable at the top of an execution block, and not using it for a hundred lines of code, gives lots of space for someone to accidentally change the value without realizing the dependency. Having it on the next line makes it clear why the variable is initialized the way it is and where it is used. A Berkeley study showed that a variable declared more than 7 lines from its usage had an extremely high chance (> 80%) of being used incorrectly.

  • Function length. Functions should not be longer than 60 lines long. This number is arbitrary but the whole function will fit on a single printed page. If you find your function is longer, think about how the task can be refactored. Use smaller functions and inline functions to avoid call overhead.

  • Address compiler warnings. Compiler warning messages mean something is not as it should be. Fix what the compiler is complaining about. If you absolutely cannot address it, use #pragma to suppress the warning; this is a remedy of last resort.

  • Never allow FLOAT to implicit convert to INT. This is a slow operation, and does not compile on all compilers. Instead, always use the appTrunc() function to convert to INT. This will ensure cross-compiler compatibility as well as generate faster code.

Namespaces in C++


  • You can use namespaces to organize your classes, functions and variables where appropriate, as long as you follow the rules below.

  • Unreal code is currently not wrapped in a global namespace. You need to watch out for collisions in the global scope, especially when pulling in third party code.

  • Don't put "using" declarations in the global scope, even in a .cpp file (it will cause problems with our "unity" build system.)

  • It's fine to put "using" declarations within another namespace, or within a function body.

  • Note that if you put "using" within a namespace, it will carry over to other occurrences of that namespace in the same translation unit. As long as you're consistent it will be fine, though.

  • You can only use "using" in header files safely if you follow the above rules.

  • Note that forward-declared types need to be declared within their respective namespace, otherwise you'll get link errors.

  • If you declare a lot of classes/types within a namespace, it can make it difficult to use those types in other global-scoped classes. (e.g. function signatures will need to use explicit namespace when appearing in class declarations.)

  • You can use "using" to alias only specific variables within a namespace into your scope (e.g. using Foo::FBar), but we don't usually do that in Unreal code.

  • It's often useful to wrap enum declarations in a namespace (C#-style scoping). See below for an example.

Namespace Example (Scoped enums)

/**
 * Defining a enumeration within a namespace to achieve C#-style enum scoping
 */
namespace EColorChannel
{
    /** Declare EColorChannel::Type as the actual type for this enum */
    enum Type
    {
        /** Red color channel */
        Red,

        /** Green color channel */
        Green,

        /** Blue color channel */
        Blue
    };
}


/**
 * Given a color channel, returns a name string for that color
 *
 * @param   InColorChannel   The color channel to return a name for
 *
 * @return  Name of this color channel
 */
FString GetNameForColorChannel( const EColorChannel::Type InColorChannel )
{
    FString Name;

    switch( InColorChannel )
    {
        case EColorChannel::Red:
            Name = TEXT( "Red" );
            break;

        case EColorChannel::Green:
            Name = TEXT( "Green" );
            break;

        case EColorChannel::Blue:
            Name = TEXT( "Blue" );
            break;
    }

    return Name;
}

Examples:


/**
 * This class handles various waveform activities. It manages the waveform data
 * that is being played on any given gamepad at any given time. It is called by
 * the player controller to start/stop/pause a waveform for a given gamepad. It
 * is called by the UXboxViewport to get the current rumble state information
 * to apply that to the gamepad. It does this by evaluating the function
 * defined in the waveform sample data.<BR>
 *
 * Copyright:    Copyright (c) 2005<BR>
 * Company:      Epic Games Inc.<BR>
 */
class WaveformManager extends Object within PlayerController
   native
   transient
   poolable(1,0);

/**
 * Whether the player has disabled gamepad rumble or not (TCR C5-3).  This comment is 
 * long so it really needs the multiple lines.
 */
var bool bAllowsForceFeedback;

/** The currently playing waveform */
var ForceFeedbackWaveform FFWaveform;

/**  Whether it was paused by the player controller or not */
var bool bIsPaused;

/** The current waveform sample being played */
var int CurrentSample;

/** The amount of time elapsed since the start of this waveform */
var float ElapsedTime;

/** The amount to scale all waveforms by (user settable) */
var float ScaleAllWaveformsBy;

/**
 * Sets the waveform to play for the gamepad
 *
 * @param Waveform The waveform data to play
 */
simulated final function PlayWaveform(ForceFeedbackWaveform Waveform)
{
   // Zero out the current sample and duration and unpause if paused
   CurrentSample = 0;
   ElapsedTime = 0.0;
   bIsPaused = FALSE;
   // Make sure the waveform is valid
   if (Waveform != None && Waveform.Samples.Length > 0 &&
      bAllowsForceFeedback )
   {
      // Set the wave form to play
      FFWaveform = Waveform;
   }
   else
   {
      FFWaveform = None;
   }
}

/**
 * Stops the waveform by nulling out the waveform
 */
simulated final function StopWaveform()
{
   // Remove the current waveform
   FFWaveform = None;
}

/**
 * Pauses/unpauses the playback of the waveform for the gamepad
 *
 * @param bPause TRUE to pause, FALSE to resume
 */
simulated final function PauseWaveform(optional bool bPause)
{
   // Set the paused state for the gamepad
   bIsPaused = bPause;
}

defaultproperties
{
   bAllowsForceFeedback=TRUE
   ScaleAllWaveformsBy=1.0
}

C++ functions


/**
 * Run Post render for each sub camera effect and accumulate results.
 * By doing this we save the extra full screen pass from adding the emissive 
 * result and we also get an extra 2 pass blur on the emissive for free.
 *
 * @param Viewport   The viewport we are drawing into
 * @param RI      The render interface used for drawing
 */
void UUCScreenEmissiveWithBloom::PostRender(UViewport* Viewport,FRenderInterface* RI)
{
   guard(UUCScreenEmissiveWithBloom::PostRender);

   checkSlow( EmissiveCamEffect && BloomCamEffect );

   // make sure that the emissive post render does not draw its
   // results onto the back buffer
   EmissiveCamEffect->DisplayOutput = 0;
   EmissiveCamEffect->PostRender( Viewport, RI );

   // set the result from the emissive camera effect for the
   // bloom camera effect to use 
   BloomCamEffect->EmissiveResultTarget =
EmissiveCamEffect->GetEmissiveResultTarget();
   BloomCamEffect->PostRender( Viewport, RI );

   unguard;
}


/**
 * Increments the int variable attached in link 0, then
 * compares it to the int variable attached in link 1,
 * and fires impulses based on the comparison.
 */
void USeqCond_Increment::Activated()
{
   check(VariableLinks.Num() >= 2 && "Decrement requires at least 2 variables");

   // grab our two int variables
   INT Value1 = 0;
   INT Value2 = 0;
   TArray<INT*> IntValues1;
   GetIntVars( IntValues1, TEXT("Counter") );
   TArray<INT*> IntValues2;
   GetIntVars( IntValues2, TEXT("Comparison") );
   // increment all of the counter variables
   for (INT VarIdx = 0; VarIdx < IntValues1.Num(); VarIdx++)
   {
      *(IntValues1(VarIdx)) += IncrementAmount;
      Value1 += *(IntValues1(VarIdx));
   }
   // get the actual values by adding up all linked variables
   for (INT VarIdx = 0; VarIdx < IntValues2.Num(); VarIdx++)
   {
      Value2 += *(IntValues2(VarIdx));
   }
   // compare the values and set output impulse
   OutputLinks(Value1 <= Value2 ? 0 : 1).bHasImpulse = 1;
}

C++ classes


/**
 * Base class for all templated arrays. Handles allocation of dynamic arrays and
 * uses an algorithm to decide how much "slack" memory to allocate to prevent
 * lots of copying of data due to inefficient grow patterns.
 */
class FArray
{
public:
   /**
    * Allows access to the array block
    *
    * @return The pointer to the block of memory that makes up the array
    */
   void* GetData()
   {
      return Data;
   }
   
   /**
    * Validates that the specified index is within the array boundary
    *
    * @return TRUE if the index is valid, FALSE otherwise
    */
   UBOOL IsValidIndex( INT i ) const
   {
      return i>=0 && i<ArrayNum;
   }
   
   // ...
   
protected:
   /** The pointer to the block of memory representing the array */
   void*    Data;
   /** Number of elements currently in the array */
   INT      ArrayNum;
   /** The number of elements the array can hold without having to realloc */
   INT      ArrayMax;
};