This document summarizes the high-level coding conventions for writing Unreal 4 client code at Daedalic Entertainment. They are based on the official Unreal Coding Standard:
The goal is to make it easier to work in similar teams inside and outside the company, as well as have client code blend in with other code of the Unreal API. We are providing a complete summary here in order to allow people to understand the conventions at a glance, instead of having to open multiple documents. Our coding conventions are numbered, which makes it easier to refer to them in code reviews.
In case we've missed recent changes to the official Unreal Coding Standard, or you can spot any other issue, please create a pull request.
1.1. DO NOT use namespaces to organize your game classes. Namespaces are not supported by UnrealHeaderTool, so they can't be used when defining UCLASSes, USTRUCTs etc., and we don't want to put half of our game code in a namespace and the other half in global scope.
1.2. DO add a prefix to all class and struct names, based on your project name.
2.1. DO use PascalCase for file names, with the project name prefix, but without the type prefix (e.g. AHOATCharacter goes into HOATCharacter.h).
2.2. DO write header files with the following structure:
#pragma once#includeof the pre-compiled header, if any (e.g.#include "HOATPCH.h")#includeof the base class header, if any (e.g.#include "GameFramework/Character.h")#includeof the generated class header (e.g.#include "HOATCharacter.generated.h")- delegate declarations
- forward declarations for any referenced engine or game types
- type definition
2.3. DO define classes with the following structure:
- constructors
- destructor
- public
overridefunctions (e.g.BeginPlay,Tick,GetLifetimeReplicatedProps) - public functions
- public event handlers
virtual void Notify...functionsUFUNCTION(BlueprintImplementableEvent) void Receive...functionsUPROPERTY(BlueprintAssignable)delegate properties
- operators
- public constants
- public
UPROPERTYs - protected
overridefunctions - protected functions
- protected constants
- protected fields
- private functions
- private constants
- private fields
Within each of these groups, order members by name or logical groups.
2.4. DO NOT cover more than a single feature in each file. Don't define more than one public type per file.
2.5. DO leave a blank line at the end of the file to play nice with gcc.
3.1. DO NOT include unused headers. This will generally help reduce the compilation time, especially for developers when just one header has been modified. It may also avoid errors that can be caused by conflicts between headers. If an object in the class is only used by pointer or by reference, it is not required to include the header for that object. Instead, just add a forward declaration before the class.
3.2. DO NOT rely on a header that is included indirectly by another header you include.
4.1. DO use PascalCase for class names, e.g. AHOATCharacter.
4.2. DO add type prefixes to class names to distinguish them from variable names. For example, FSkin is a type name, and Skin is an instance of a FSkin. UnrealHeaderTool requires the correct prefixes in most cases, so it's important to provide them.
- Template classes are prefixed by
T. - Classes that inherit from
UObjectare prefixed byU. - Classes that inherit from
AActorare prefixed byA. - Classes that inherit from
SWidgetare prefixed byS. - Classes that are abstract interfaces are prefixed by
I. - Enums are prefixed by
E. - Most other classes are prefixed by
F, though some subsystems use other letters.
4.3. DO prefix boolean variables by b (e.g. bPendingDestruction or bHasFadedIn).
4.4. DO uppercase the first letter of acronyms, only, e.g. FHOATXmlStreamReader, not FHOATXMLStreamReader.
4.5. DO add a virtual destructor to classes with virtual member functions.
4.6. DO mark classes that are not meant to be derived from as final. This should be the default for non-interface classes. Care has to be taken when removing the final keyword from a class when inheritance is required. Classes that are already derived don't need to be marked as final by default: In the most common case there is no reason to prevent further inheritance.
4.7. DO use a non-virtual destructor in final classes unless they are already derived.
4.8. DO use structs for data containers, only. They shouldn't contain any business logic beyond simple validation or need any destructors.
5.1. DO mark each constructor that takes exactly one argument as explicit, unless it's a copy constructor or the whole point of the constructor is to allow implicit casting. This minimizes wrong use of the constructor.
6.1. DO use PascalCase for functions names.
6.2. DO NOT add a space between function name and parameter list:
// Right:
void Tick(float DeltaSeconds);
// Wrong:
void Tick (float DeltaSeconds);
6.3. DO pass each object parameter that is not a basic type (int, float, bool, enum, or pointers) by reference-to-const. This is faster, because it is not required to do a copy of the object. Also, this is required for exposing the property in blueprints:
bool GetObjectsAtWorldPosition(const FVector& WorldPositionXY, TArray<FHitResult>& OutHitResults);
6.4. DO prefix function parameter names with Out if they are passed by reference and the function is expected to write to that value. This makes it obvious that the value passed in this argument will be replaced by the function. If an Out parameter is also a boolean, put the b before the Out prefix, e.g. bOutResult.
6.5. DO flag methods as const if they do not modify the object.
6.6. CONSIDER writing functions with six parameters or less. For passing more arguments, try and use structs instead, and/or refactor your function.
6.7. CONSIDER using enum values instead of boolean function parameters.
// Right:
ShowMessageBox(TEXT("Nice Title"), TEXT("Nice Text"), MessageBox::MESSAGEBOX_BUTTONS_OK);
// Wrong: Meaning of third parameter is not immediately obvious.
ShowMessageBox(TEXT("Nice Title"), TEXT("Nice Text"), false);
6.8. AVOID providing function implementations in header files. Use inline functions judiciously, as they force rebuilds even in files which don't use them. Inlining should only be used for trivial accessors and when profiling shows there is a benefit to doing so. Be even more conservative in the use of FORCEINLINE. All code and local variables will be expanded out into the calling function and will cause the same build time problems caused by large functions. Don't use inlining or templates for functions which are likely to change over a hot reload.
7.1. DO use PascalCase for variable names.
7.2. AVOID short or meaningless names (e.g. A, Rbarr, Nughdeget). Single character variable names are only okay for counters and temporaries, where the purpose of the variable is obvious.
7.3. DO NOT use negative names for boolean variables.
// Right:
if (bVisible)
// Wrong: Double negation is hard to read.
if (!bInvisible)
7.4. DO declare each variable on a separate line so that a comment on the meaning of the variable can be provided. Also, the JavaDoc style requires it.
7.5. DO use portable aliases for basic C++ types:
boolfor boolean values (never assume the size ofbool).BOOLwill not compile.TCHARfor a character (never assume the size ofTCHAR).uint8for unsigned bytes (1 byte).int8for signed bytes (1 byte).uint16for unsigned "shorts" (2 bytes).int1for signed "shorts" (2 bytes).uint32for unsigned ints (4 bytes).int32for signed ints (4 bytes).uint64for unsigned "quad words" (8 bytes).int64for signed "quad words" (8 bytes).floatfor single precision floating point (4 bytes).doublefor double precision floating point (8 bytes).PTRINTfor an integer that may hold a pointer (never assume the size ofPTRINT).
7.6. DO put a single space between the * or & and the variable name for pointers or references, and don't put a space between the type and * or &. For us, the fact that we are declaring a pointer or reference variable here much more belongs to the type of the variable than to its name:
AController* Instigator
const FDamageEvent& DamageEvent
7.7. DO test whether a pointer is valid before dereferencing it. nullptr should be used instead of the C-style NULL macro in all cases. If the pointer points to any UOBJECT, use IsValid to ensures the pointer is not null and the referenced object is not marked for destruction.
8.1. CONSIDER using enum class over static constexpr over static const variables over #define when defining constants.
8.2. DO use ALL_CAPS with underscores between words for constant names.
9.1. DO use four spaces for indentation.
9.2. DO use a single space after a keyword and before a parenthesis.
// Right:
if (bVisible)
{
}
// Wrong:
if(bVisible)
{
}
9.3. DO surround binary operators with spaces.
9.4. DO NOT put multiple statements on one line.
10.1. CONSIDER keeping lines shorter than 100 characters; wrap if necessary.
10.2. DO use a new line for the body of a control flow statement:
// Right:
if (bVisible)
{
Hide();
}
// Wrong:
if (bVisible) Hide();
10.3. DO start operators at the beginning of the new lines.
// Right:
if (longExpression
+ otherLongExpression
+ otherOtherLongExpression)
{
}
// Wrong: Operator at the end of the line is easy to miss if the editor is too narrow.
if (longExpression +
otherLongExpression +
otherOtherLongExpression)
{
}
11.1. DO put opening braces on their own lines:
// Right:
if (bVisible)
{
}
else
{
}
// Wrong:
if (bVisible) {
} else {
}
11.2. DO have the left brace on the start of a line for class declarations and function definitions:
void AHOATCharacter::Tick(float DeltaSeconds)
{
}
class Moo
{
};
11.3. DO use curly braces, even if the body of a conditional statement contains just one line:
// Right:
if (bVisible)
{
Hide();
}
// Wrong: Can lead to subtle bugs in the future, if the body is extended to span multiple statements.
if (bVisible)
Hide();
12.1. DO use parentheses to group expressions:
// Right:
if ((a && b) || c)
// Wrong: Operator precedence is not immediately clear.
if (a && b || c)
12.3. DO NOT use spaces after parentheses:
// Right:
if ((a && b) || c)
// Wrong:
if ( ( a && b ) || c )
13.1. DO add a break (or return) statement at the end of every case, or a comment to indicate that there's intentionally no break, unless another case follows immediately within switch statements
switch (MyEnumValue)
{
case Value1:
DoSomething();
break;
case Value2:
case Value3:
DoSomethingElse();
// Fall through.
default:
DefaultHandling();
break;
}
13.2. DO NOT put else after jump statements:
// Right:
if (ThisOrThat)
{
return;
}
SomethingElse();
// Wrong: Causes unnecessary indentation of the whole else block.
if (ThisOrThat)
{
return;
}
else
{
SomethingElse();
}
13.3. DO NOT mix const and non-const iterators.
// Right:
for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it)
// Wrong: Crashes on some compilers.
for (Container::const_iterator it = c.begin(); it != c.end(); ++it)
14.1. CONSIDER using the auto keyword when it avoids repetition of a type in the same statement, or when assigning iterator types. If in doubt, for example if using auto could make the code less readable, do not use auto.
auto* HealthComponent = FindComponentByClass<UHOATHealthComponent>();
14.2. DO use auto* for auto pointers, to be consistent with references, and to add additional guidance for the reader.
14.3. DO use proprietary types, such as TArray or TMap where possible. This avoids unnecessary and repeated type conversion while interacting with the Unreal Engine APIs.
14.4. DO use the TEXT() macro around string literals. Without it, code which constructs FStrings from literals will cause an undesirable string conversion process.
15.1. CONSIDER exposing meaningful events to subclasses and/or other interested listeners by defining virtual functions and/or multicast delegates.
15.2. DO define two functions when exposing an event to a subclass. The first function should be virtual and its name should begin with Notify. The second function should be a BlueprintImplementableEvent UFUNCTION and its name should begin with Receive. The default implementation of the virtual function should be to call the BlueprintImplementableEvent function (see AActor::NotifyActorBeginOverlap and AActor::ReceiveActorBeginOverlap for example).
15.3. DO call the virtual function before broadcasting the event, if both are defined (see UPrimitiveComponent::BeginComponentOverlap for example).
Example:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FHoatActorGraphConnectivityChangedSignature, AActor*, Source, AActor*, Target, float, Distance);
/** Event when the connectivity of an observed source vertex has changed. */
virtual void NotifyOnConnectivityChanged(AActor* Source, AActor* Target, float Distance);
/** Event when the connectivity of an observed source vertex has changed. */
UFUNCTION(BlueprintImplementableEvent, Category = Graph, meta = (DisplayName = "OnConnectivityChanged"))
void ReceiveOnConnectivityChanged(AActor* Source, AActor* Target, float Distance);
/** Event when the connectivity of an observed source vertex has changed. */
UPROPERTY(BlueprintAssignable)
FHoatActorGraphConnectivityChangedSignature OnConnectivityChanged;
void AHoatActorGraph::NotifyOnConnectivityChanged(AActor* Source, AActor* Target, float Distance)
{
ReceiveOnConnectivityChanged(Source, Target, Distance);
OnConnectivityChanged.Broadcast(Source, Target, Distance);
HOAT_LOG(hoat, Log, TEXT("%s changed the connectivity of vertex %s: Distance to target %s changed to %f."),
*GetName(), *Source->GetName(), *Target->GetName(), Distance);
}
16.1. DO add a space after //.
16.2. DO place the comment on a separate line, not at the end of a line of code.
16.3. DO write API documentation with Javadoc comments.
17.1. DO NOT use any swearing in symbol names, comments or log output.