Gameplay Debugger: Quick, simple, re-usable

Introduction This tutorial will help you quickly set up a re-usable C++ Gameplay Debugger Category, which can be used against any Actor Component without the need to rewrite code. Once you've copie...

Updated over 4 years ago Edit Page Revisions

Introduction

This tutorial will help you quickly set up a re-usable C++ Gameplay Debugger Category, which can be used against any Actor Component without the need to rewrite code.

Once you've copied and pasted the new class into your game, you'll need to type one line in your Game Instance class to register your new category, and one function to spit out text from your Actor Component.

Overview

The UE4 Gameplay debugger provides a powerful real-time visual debugging layer to be overlayed on top of your game:

You bring it up by pressing the apostrophe character ('). You've probably used it already to take advantage of the in-built gameplay debugger categories, such as NavMesh, Behaviour or EQS Queries.

However the Gameplay Debugger becomes really useful when instructed how to visualise your own proprietary classes. If you've read the page above, or looked at the source code, chances are it seems a bit heavy-going (and out-of-date!). Sure you can copy and paste all that code, switching out what you need. Sure you can do this for every single actor component you want to debug. But you'd much rather have something that works out of the box, typing a few lines of code right?

This implementation helps you do just that. In a minimum of two lines, you can have a new Gameplay Debugger Category set up and associated with a hotkey.

File:GameplayDebuggerCategory.png

The code included in this tutorial assumes that you'll want to debug an '''Actor Component'''. For us, that's the case almost all of the time: we try to adhere to a [https://en.wikipedia.org/wiki/Strategy_pattern Strategy pattern] when coding functionality for actors. Doing our leg-work in Actor Components allows us to do this and reuse as much as possible. It keeps our classes simple by honouring the [https://en.wikipedia.org/wiki/Single_responsibility_principle Single Responsibility Principle], whilst having the added advantage of integrating with the Unreal Editor very well. Of course if your game has a different structure, you're welcome to make changes!|tip

Requirements

Engine Version

Requires Unreal Engine 4.12. The code below has been tested on version 4.14.3, 4.15.3 & 4.16.2

Getting Started

Copy the two files below to create the new FGameplayDebuggerCategory class. Register and define your debug function and you're all set up.

GameplayDebuggerCategoryT.h

Copy and paste the following code and make a new file called GameplayDebuggerCategoryT.h. Ensure it's included in your UE4 game project (or in a library).

// GameplayDebuggerCategoryT.h
#pragma once

#include "GameplayDebuggerCategory.h"

// Gameplay debugger category to show information when the gameplay debugger is shown in game
// (apostrophe hotkey). This templatised class can be used for debugging any Actor Component.
// Instantiate this class by calling the static Register function from your GameInstance,
// implement a new function on your actor component with the signature: 
// FString GetDebugInfoString()
template
class FGameplayDebuggerCategoryT : public FGameplayDebuggerCategory
{
public:
   FGameplayDebuggerCategoryT();

   // Call this to register your category in the Gameplay Debugger
   static void Register(const FName& Name, int Hotkey);

   virtual void CollectData(APlayerController* OwnerPC, AActor* DebugActor) override;
   virtual void DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) override;
   virtual void OnDataPackReplicated(int32 DataPackId) override;

protected:
   static TSharedRef MakeInstance();

   struct FDebugData
   {
      TArray DebugStrings;
      void Serialize(FArchive& archive);
   } 
   DebugData;
};

#include "GameplayDebuggerCategoryT.inl"

GameplayDebuggerCategoryT.inl

Copy and paste the following code and make a new file called GameplayDebuggerCategoryT.inl. Ensure it's included in your UE4 game project (or in a library).

// GameplayDebuggerCategoryT.inl
#pragma once

#include "GameplayDebuggerCategoryT.h"
#include "GameUtils.h"
#include "GameplayDebugger.h"
#include "SharedPointerInternals.h"

///////////////////////////////////////////////////////////////////////////////
// FGameplayDebuggerCategoryT factory instantiation
///////////////////////////////////////////////////////////////////////////////
template
inline FGameplayDebuggerCategoryT::FGameplayDebuggerCategoryT()
{
   SetDataPackReplication(&DebugData);
}

template
inline void FGameplayDebuggerCategoryT::Register(const FName& Name, int Hotkey)
{
   const auto STATE_FLAGS = EGameplayDebuggerCategoryState::EnabledInGame;

   auto& debugger = IGameplayDebugger::Get();
   auto creationDelegate = IGameplayDebugger::FOnGetCategory::CreateStatic(&FGameplayDebuggerCategoryT::MakeInstance);

   debugger.RegisterCategory(Name, creationDelegate, STATE_FLAGS, Hotkey);
   debugger.NotifyCategoriesChanged();
}

template
inline TSharedRef FGameplayDebuggerCategoryT::MakeInstance()
{
   return MakeShareable(new FGameplayDebuggerCategoryT());
}

///////////////////////////////////////////////////////////////////////////////
// FGameplayDebuggerCategoryT
///////////////////////////////////////////////////////////////////////////////
template
inline void FGameplayDebuggerCategoryT::CollectData(APlayerController* Owner, AActor* DebugActor)
{
   DebugData.DebugStrings.Empty();
   if (!DebugActor) return;

   for (UActorComponent* component : DebugActor->GetComponents())
   {
      T* componentCasted = Cast(component);
      if (componentCasted)
      {
         DebugData.DebugStrings.Add(componentCasted->GetDebugInfoString());
      }
   }
}

template
inline void FGameplayDebuggerCategoryT::OnDataPackReplicated(int32 DataPackId)
{
   MarkRenderStateDirty();
}

template
inline void FGameplayDebuggerCategoryT::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext)
{
   for(const FString& debugInfoString : DebugData.DebugStrings)
   {
      CanvasContext.Printf(*debugInfoString);
   }
}

///////////////////////////////////////////////////////////////////////////////
// FGameplayDebuggerCategoryT::FDebugData
///////////////////////////////////////////////////////////////////////////////
template
inline void FGameplayDebuggerCategoryT::FDebugData::Serialize(FArchive& archive)
{
   for (auto debugString : DebugStrings)
   {
      archive < debugString;
   }
}

Use the new Gameplay Debugger Category class

Implement GetDebugInfoString on your Actor Component

This is where the magic happens. You'll of course have to write your own GetDebugInfoString function on your own specialised Actor Component. A real-world example of how to use is however provided.

FString UVehicleMovementComponent::GetDebugInfoString() const
{
   check(0