I have changed a bunch of my coding standards over teh year. The most important thing is usually to follow whatever your company does. That being said, these are some things that I have grown to adopt.

All of these are suggestions, things to try out. I'm describing things that work for me, not telling you how you "must" do things.

Includes

Include What You Use

Unreal Engine moved to Include What You Use (shortened to "IWYU") at least a while ago. At first I wasn't really sure what it meant in concrete terms.

For me I understand it to mean only include headers where you absolutely have to.

  1. Don't #include "CoreMinimal.h"
  2. Forward declare whereever possible

Forward Declare

Forward declaration is well discussed online but the TL;DR version is:

  • You can forward declare something when C++ doesn't need to know the size of it in memory

Example.h

enum class EDogState : uint8;
class UDog;
struct FDogInfo;

// Later in file
void RegisterDog(UDog* Dog);
void SetupDog(const FDogInfo& DogInfo);
void SetDogState(EDogState DogState);

Sort Includes

Sort your #include statements alphabetically. It makes it way less likely that merge conflicts will happen, and way easier to fix them if they do.

Forward declare, don't inline declare

I used to get lazy and put class directly in front of where I was using the class in the header file. This gets really noisy really fast. Rider will automatically add the forward declare for you in the right place so there's not much excuse not to do it.

Inline forward declaration

UCLASS()
class ADog : public AActor
{
	GENERATED_BODY()

	UPROPERTY()
	TObjectPtr<class UWoofComponent> WoofComponent;
};

class UWoofComponent;

UCLASS()
class ADog : public AActor
{
	GENERATED_BODY()

	UPROPERTY()
	TObjectPtr<UWoofComponent> WoofComponent;
};

Use TObjectPtr<T>

This was released with Unreal Engine 5 if I remember correctly.

TObjectPtr is the new hotness

// Old raw pointer
UPROPERTY()
ADog* Dog;

// New TObjectPtr<T>
UPROPERTY()
TObjectPtr<ADog> Dog;

Code Style

Inline assignment

Like all of the suggesions here this is purely subjective but I've gotten used to it and kind of like it.

How I used to write code

UFlower* Flower = Garden->FindFlower(FlowerType);
if (Flower)
{
	// Do stuff with flower
}

Preferred

if (UFlower* Flower = Garden->FindFlower(FlowerType))
{
	// Do stuff with flower
}

Don't Export Everything

In a lot of my earlier code I used code generators or copied tutorials online and ended up adding the API macros at the start of my header MYPROJECT_API Instead, only export the symbols (functions) that other modules need.

The table below illustrates different macros in increasing order of how much gets exported.

1.NothingNothing is exported, neither the class nor its functions will be usable in other modules
2.UCLASS(MinimalAPI)It is possible to use Cast<T> on instances of this class in other modules.
3.MYPROJECT_API on specific functionsOnly functions marked with the macro will be callable in other modules.
4.MYPROJECT_API on classAll functions are exported.

1. Export Nothing

UCLASS()
class ADog : public AActor
{
	GENERATED_BODY()

	void Woof();
	void RollOver();
};

2. MinimalAPI

UCLASS(MinimalAPI)
class ADog : public AActor
{
	GENERATED_BODY()

	void Woof();
	void RollOver();
};

3. Export Specific Functions

UCLASS()
class ADog : public AActor
{
	GENERATED_BODY()

	MYPROJECT_API void Woof();
	void RollOver();
};

4. Export Everything

UCLASS()
class MYPROJECT_API ADog : public AActor
{
	GENERATED_BODY()

	void Woof();
	void RollOver();
};

Only use dynamic delegates where needed

I used to make all my deligates DYNAMIC Now I think about whether it needs to be exposed to Blueprints or not.

Avoiding Crashes

IsValidIndex before accessing TArray<T>

Even if you think your math is valid, I would seriously recommend wrapping all your calls to TArray<T> elements with a call to IsValidIndex. There's nothing worse than a game breaking crash on a build that took hours and hours to create.

Accessing an array element without checking

const int32 Index = ResultOfCalculation();
// If your math is wrong, crash and burn
SomeArray[Index].RiskyBusiness = 1;

Using IsValidIndex before accessing

const int32 Index = ResultOfCalculation();
if (SomeArray.IsValidIndex(Index))
{
	SomeArray[Index].DoSomething;
}