Creating an extension to the on-screen debugger in an editor module

My current goal is to create an editor for displaying debugging information. The editor itself by default supports certain categories like AI, Navigation and Perception and I wish to extend those to display my own subsystems.

I took the AIModule as an example of how to extend the gameplay debugger system, so excuse me for that mess, but for some reason I can’t get around a LNK2020/2019 error; “unresolved external symbol “public: __cdecl FVisualLoggerExtension::FVisualLoggerExtension(void)” (??0FVisualLoggerExtension@@QEAA@XZ) referenced in function InitializeModule”

The error I’m getting only occurs when I am attempting to use the “FVisualLoggerExtension” class from my editor module. My first thought was that I had to include a dependency but it doesn’t make any difference so far.

I am very confused why this is happening and I hope someone could tell me what the problem is or if I’m actually missing a dependency?

For reference I am adding source code of the current state.

GameCoreEditor.Build.cs

using System.IO;
using UnrealBuildTool;

public class GameCoreEditor : ModuleRules
{
	public GameCoreEditor(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
		OptimizeCode = CodeOptimization.InShippingBuildsOnly;

		IncludeSubDirectoriesRecursive(ModuleDirectory + "\\Private", true);
		IncludeSubDirectoriesRecursive(ModuleDirectory + "\\Public", false);

		PrivateDependencyModuleNames.AddRange(new string[] {
			"Engine",
			"Core",
			"CoreUObject",
			"ApplicationCore",
			"AIModule",
			"AssetTools",
			"PropertyEditor",
			"LevelEditor",
			"DetailCustomizations",
		});

		PublicDependencyModuleNames.AddRange(new string[] {
			"Engine",
			"Core",
			"CoreUObject",
			"ApplicationCore",
			"AIModule",
			"AssetTools",
			"PropertyEditor",
			"LevelEditor",
			"DetailCustomizations",
			"GameCore",
		});

		if (Target.bBuildEditor == true) {
			PrivateDependencyModuleNames.Add("UnrealEd");
		}

		if (Target.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test)) {
			PrivateDependencyModuleNames.Add("GameplayDebugger");
			PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1");
		}
		else {
			PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0");
		}
	}

	private void IncludeSubDirectoriesRecursive(string DirectoryPathToSearch, bool bIsPrivate) {
		foreach (string DirectoryPath in Directory.GetDirectories(DirectoryPathToSearch)) {
			if (bIsPrivate) {
				PrivateIncludePaths.Add(DirectoryPath);
			}
			else {
				PublicIncludePaths.Add(DirectoryPath);
			}
			IncludeSubDirectoriesRecursive(DirectoryPath, bIsPrivate);
		}
	}
}

GameCoreEditor.h

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#if WITH_EDITOR
#include "Developer/AssetTools/Public/AssetTypeCategories.h"
#endif // WITH_EDITOR


class IGameCoreEditorModule : public IModuleInterface {
public:
	static inline IGameCoreEditorModule& Get() {
		return FModuleManager::LoadModuleChecked<IGameCoreEditorModule>("GameCoreEditor");
	}

	static inline bool IsAvailable() {
		return FModuleManager::Get().IsModuleLoaded("GameCoreEditor");
	}

#if WITH_EDITOR
	virtual EAssetTypeCategories::Type GetEcoSAssetCategoryBit() const = 0;
#endif // WITH_EDITOR
};

GameCoreEditor.cpp

#include "GameCoreEditor.h"
#include "AIModule.h"

#include "EngineDefines.h"
#include "Templates/SubclassOf.h"
#include "AISystem.h"
#include "VisualLogger/VisualLogger.h"
#include "GameFramework/WorldSettings.h"
#include "DefaultManagerInstanceTracker.h"

#if WITH_EDITOR
#include "Developer/AssetTools/Public/IAssetTools.h"
#include "Developer/AssetTools/Public/AssetToolsModule.h"
#if ENABLE_VISUAL_LOG
#include "VisualLoggerExtension.h"
#endif // ENABLE_VISUAL_LOG
#endif // WITH_EDITOR

#if WITH_GAMEPLAY_DEBUGGER
#include "GameplayDebugger.h"
#include "DebuggerCategory_EcoSubsystem.h"
#endif // WITH_GAMEPLAY_DEBUGGER

#define LOCTEXT_NAMESPACE "GameCoreEditor"

DEFINE_LOG_CATEGORY_STATIC(GameCoreEditor, Log, All);
//DEFINE_LOG_CATEGORY(LogManagerInstanceTracker);


class FGameCoreEditorModule : public IGameCoreEditorModule {
	// Begin IModuleInterface
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	// End IModuleInterface
#if WITH_EDITOR
	virtual EAssetTypeCategories::Type GetEcoSAssetCategoryBit() const override { return EcoSAssetCategoryBit; }
protected:
	EAssetTypeCategories::Type EcoSAssetCategoryBit;
#if ENABLE_VISUAL_LOG
	FVisualLoggerExtension VisualLoggerExtension;
#endif // ENABLE_VISUAL_LOG
#endif // WITH_EDITOR
};

IMPLEMENT_MODULE(FGameCoreEditorModule, GameCoreEditor)

void FGameCoreEditorModule::StartupModule() {
#if WITH_EDITOR 
	if (GIsEditor) 	{
		// Register asset types
		IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();

		//EcoSAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("EcoSubsystem")), LOCTEXT("AIAssetCategory", "Artificial Intelligence"));
		EcoSAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("EcoSubsystem")), FText::FromString("EcoSubsystem"));
	}
#endif // WITH_EDITOR 

#if WITH_EDITOR && ENABLE_VISUAL_LOG
	FVisualLogger::Get().RegisterExtension(*EVisLogTags::TAG_EQS, &VisualLoggerExtension);
#endif

#if WITH_GAMEPLAY_DEBUGGER
	IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
	GameplayDebuggerModule.RegisterCategory("EcoSubsystem", IGameplayDebugger::FOnGetCategory::CreateStatic(&FGameplayDebuggerCategory_EcoSubsystem::MakeInstance), EGameplayDebuggerCategoryState::EnabledInGameAndSimulate, 1);
	GameplayDebuggerModule.NotifyCategoriesChanged();
#endif
}

void FGameCoreEditorModule::ShutdownModule() {
#if WITH_EDITOR && ENABLE_VISUAL_LOG
	FVisualLogger::Get().UnregisterExtension(*EVisLogTags::TAG_EQS, &VisualLoggerExtension);
#endif

#if WITH_GAMEPLAY_DEBUGGER
	if (IGameplayDebugger::IsAvailable()) {
		IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
		GameplayDebuggerModule.UnregisterCategory("EcoSubsystem");
		GameplayDebuggerModule.NotifyCategoriesChanged();
	}
#endif
}


#undef LOCTEXT_NAMESPACE

Figured it out, above is garbage code, the visual logger code is not required at all.
Registration / Unregistration of a FGameplayDebuggerCategory goes in the module like this:

void FGameCoreEditorModule::StartupModule() {
#if WITH_GAMEPLAY_DEBUGGER
    IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
    GameplayDebuggerModule.RegisterCategory("YourCategoryName", IGameplayDebugger::FOnGetCategory::CreateStatic(&FGameplayDebuggerCategory_YourCategory::MakeInstance), EGameplayDebuggerCategoryState::EnabledInGameAndSimulate, -1);
    GameplayDebuggerModule.NotifyCategoriesChanged();
#endif
}

void FGameCoreEditorModule::ShutdownModule() {
#if WITH_GAMEPLAY_DEBUGGER
	if (IGameplayDebugger::IsAvailable()) {
		IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
		GameplayDebuggerModule.UnregisterCategory("YourCategoryName");
		GameplayDebuggerModule.NotifyCategoriesChanged();
	}
#endif
}

anything else goes into a class deriving from FGameplayDebuggerCategory. I missed some overridable settings on that base class which caused nothing to print onscreen…

1 Like