Skip to content

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

What Clang-Format Handles (Automatic)

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

Header Files (.h)

#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

Header Organization

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"

When Full Includes Are Required in Headers

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

Complete Header Example

#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