C++ Style Guide
Summary: Coding standards for Project Eternal C++ code. These rules ensure consistency, readability, and maintainability across the codebase. Automated formatting is handled by .clang-format; this document covers structural decisions that require human judgment.
Table of Contents
Automated vs Manual Rules
Run clang-format -i <file> or configure your IDE to format on save.
| Rule |
Setting |
| Indentation |
Tabs, width 4 |
| Braces |
Allman style (own line) |
| Line length |
132 characters |
| Include sorting |
Alphabetical within groups |
| Pointer alignment |
Left (int* ptr not int *ptr) |
| Spacing |
Around operators, after keywords |
What Requires Manual Attention
These structural decisions cannot be automated:
| Rule |
Description |
| Access specifier order |
public → protected → private |
| Forward declarations |
Prefer over includes in headers |
| Method grouping |
Group by purpose/interface |
| Inline vs cpp |
Trivial accessors inline, logic in cpp |
| Virtual keywords |
Know when to use virtual/override |
File Structure
#pragma once
#include "CoreMinimal.h"
// Engine includes (alphabetical)
// Project includes (alphabetical)
#include "ClassName.generated.h" // MUST be last
// Forward declarations (alphabetical by type)
class UForwardDeclaredClass;
class AForwardDeclaredActor;
struct FForwardDeclaredStruct;
// Delegate declarations
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDelegate);
UCLASS()
class PROJECTETERNAL_API AMyClass : public ABase
{
GENERATED_BODY()
// ... class body
};
Implementation Files (.cpp)
#include "Path/To/MyClass.h" // Matching header FIRST
// All other includes (alphabetical)
#include "Other/Header.h"
#include "Another/Header.h"
// Implementation follows
Key Rules
| Rule |
Rationale |
| No BOM characters |
Use UTF-8 without BOM |
| No copyright boilerplate |
Project-specific, not Epic template |
.generated.h last |
UHT requirement |
| Matching header first in cpp |
Ensures header is self-contained |
Include Strategy
In Headers: Forward declare aggressively
// GOOD - forward declaration
class UInventoryComponent;
// AVOID - full include (unless needed for inheritance/inline)
#include "Inventory/Components/InventoryComponent.h"
In CPP Files: Include everything needed
#include "Inventory/Components/InventoryComponent.h"
#include "Inventory/Items/ItemObject.h"
| Situation |
Example |
| Base class |
#include "GameFramework/Character.h" |
| Enum types |
#include "AbilitySystem/Data/CharacterClassInfo.h" |
| Inline method bodies |
Need complete type |
| Template arguments |
Need complete type |
| UPROPERTY with TSubclassOf |
TSubclassOf<UGameplayEffect> |
Class Structure
Access Specifier Order
Always use: public → protected → private
UCLASS()
class AMyClass : public ABase
{
GENERATED_BODY()
public:
// 1. Constructor/Destructor
// 2. Static methods
// 3. Base class overrides (grouped by base)
// 4. Interface implementations (grouped by interface)
// 5. Public methods
// 6. Accessors (inline)
// 7. Delegates
// 8. Public properties
protected:
// 1. Protected overrides
// 2. Protected methods
// 3. Protected properties
private:
// 1. Private methods
// 2. Private properties
};
Grouping Methods
Use single-line comments to group related methods:
public:
AMyCharacter();
// ACharacter overrides
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
// IAbilitySystemInterface
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
// ICombatInterface
virtual UCombatComponent* GetCombatComponent_Implementation() const override;
// Combat
void Attack();
void TakeDamage(float Amount);
// Accessors
UCombatComponent* GetCombat() const { return Combat; }
Accessor Guidelines
| Type |
Location |
Example |
| Trivial getter |
Inline in header |
UType* GetThing() const { return Thing; } |
| Getter with logic |
CPP file |
Casting, null checks, computation |
| Return type |
Raw pointer |
UType* not TObjectPtr<UType> |
Naming Conventions
The "Eternal" Prefix
Reserved for core framework classes only:
| Uses Eternal |
Doesn't Use Eternal |
AEternalCharacter |
UCombatComponent |
AEternalPlayer |
UInventoryComponent |
UEternalGameInstance |
UItemObject |
UEternalAbilitySystemComponent |
UPoiseSystemComponent |
Standard UE Prefixes
| Prefix |
Type |
Example |
A |
Actor |
AEternalCharacter |
U |
UObject/Component |
UCombatComponent |
F |
Struct/Delegate |
FItemManifest |
E |
Enum |
ECharacterClass |
I |
Interface |
ICombatInterface |
Variable Naming
| Type |
Convention |
Example |
| Local variable |
camelCase |
itemCount |
| Member variable |
PascalCase |
Inventory |
| Boolean |
bPascalCase |
bIsMoving |
| Pointer |
No prefix |
Combat not pCombat |
Best Practices
Pointer Validation
// PREFERRED - soft assertion for expected-valid pointers
ensure(Item);
Item->DoSomething();
// Use check() only for critical invariants
check(AbilitySystemComponent);
// Global IsValid() disambiguation when implementing ITargetable
if (!::IsValid(Combat)) // Note the :: prefix
Constructor Initialization
// Use TEXT() macro for component names
Inventory = CreateDefaultSubobject<UInventoryComponent>(TEXT("Inventory"));
// Group component creation logically
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 800.f;
TopDownCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
TopDownCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
Empty Implementations
// REMOVE empty overrides that just call Super
void AMyClass::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
// No other code - DELETE THIS OVERRIDE
}
// KEEP overrides with actual logic
void AMyClass::BeginPlay()
{
Super::BeginPlay();
InitializeSomething(); // Has purpose
}
Guard Clauses (Early Returns)
// GOOD - early return pattern
void AMyClass::DoSomething()
{
if (!IsValid(Target))
{
return;
}
if (!CanPerformAction())
{
return;
}
// Main logic here
Target->PerformAction();
}
// AVOID - deeply nested
void AMyClass::DoSomething()
{
if (IsValid(Target))
{
if (CanPerformAction())
{
Target->PerformAction();
}
}
}
Examples
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "Interface/CombatInterface.h"
#include "MyCharacter.generated.h"
class UAbilitySystemComponent;
class UCombatComponent;
class UHealthComponent;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeath, AMyCharacter*, Character);
UCLASS()
class PROJECTETERNAL_API AMyCharacter : public ACharacter,
public IAbilitySystemInterface,
public ICombatInterface
{
GENERATED_BODY()
public:
AMyCharacter();
// ACharacter overrides
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void PossessedBy(AController* NewController) override;
// IAbilitySystemInterface
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
// ICombatInterface
virtual UCombatComponent* GetCombatComponent_Implementation() const override;
// Accessors
UHealthComponent* GetHealthComponent() const { return HealthComponent; }
// Delegates
UPROPERTY(BlueprintAssignable)
FOnDeath OnDeath;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Combat")
TObjectPtr<UCombatComponent> Combat;
private:
UPROPERTY(VisibleAnywhere, Category = "Health")
TObjectPtr<UHealthComponent> HealthComponent;
UPROPERTY()
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
};
Changelog
| Date |
Change |
| 2025-01-10 |
Initial creation based on GameMode/Character refactoring |