How to access class field in directory visitor?

I am trying to write a class to get a list of the sub-directories in a content folder.

I declared this header:

#pragma once

#include "CoreMinimal.h"
#include "Settings/StructViewerSettings.h"
#include "PuzzleCategories.generated.h"

UCLASS()
class SLITHERLINK3D_API UPuzzleCategories : public UStructViewerSettings
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "Puzzles")
	TArray<FString>	getPuzzleCategories();

private:
	TArray<FString> puzzleCategories;
};

Here is my code:

#include "PuzzleCategories.h"

struct PuzzlesCategoryVisitor : public IPlatformFile::FDirectoryVisitor {
    bool Visit(const TCHAR* file, bool isDirectory) override {
        if (isDirectory) {
            UE_LOG(LogInit, Warning, TEXT("Found category: %s"), file);
            puzzleCategories.Add(file);
        }
        return true;
    }
};

TArray<FString>	UPuzzleCategories::getPuzzleCategories()
{
    if (puzzleCategories.IsEmpty() ) {
        PuzzlesCategoryVisitor categoryVisitor;
        FPlatformFileManager::Get().GetPlatformFile().IterateDirectory(
            *FPaths::ProjectContentDir().Append(TEXT("Puzzles")),
            categoryVisitor);
    }
    return puzzleCategories;
}

Visual Studio is saying the puzzleCategories is an undeclared identifier in the Visit method.

I must be misunderstand the variable scope. How do I access it?

I commented out the call to add the items to the puzzleCategories array so I can try adding my function to a blueprint.

I added a call to get the puzzle categories from my widget pre-construct event:

I am getting this warning:
Get Puzzle Categories was pruned because its Exec pin is not connected.

That is strange because there is a connection from the pre-construct event. What did I miss?

Did you add the UnrealEd module to your build file?

UStructViewerSettings needs it to function.

Check the Module section in the documentation.

I will try adding that.

Is there a better base class to use instead of UStructViewerSettings?
I did not know which one to pick since this code does not seem to fall into any of the ones I could find.

Why do you need to access the Struct Viewer loading and saving?

If you just want to save & load data then a save file would be better.

UnrealEd will probably cause problems when packing the project as it’s editor only.

I don’t. I am probably using the wrong base class.

If you base your class off of USavegame then you can just use Unreal’s built in save & load mechanics.

I am not trying to use save and load mechanics. I am just trying to declare a class to get a TArray from a list of directories in the Content folder.

I changed my header file to extend from UObject:

#pragma once

#include "CoreMinimal.h"
#include "PuzzleCategories.generated.h"

UCLASS()
class SLITHERLINK3D_API UPuzzleCategories : public UObject
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "Puzzles")
		TArray<FString>	getPuzzleCategories();

private:
	TArray<FString> puzzleCategories;
};

I still can’t access the puzzleCategories array from my cpp code.

When I go to my blueprint, I am now getting a different error:

This blueprint (self) is not a PuzzleCategories, therefore Target must have a connection.

I am not sure what to connect it to. Here is my screen:

Sorry, missed the first lines of you text and focused on the code.

Here is code from my old image lib. You should be able to filter out the directories only. For now it get specific files in the set directory, can work recursively.

Just push directory names instead of the file names and you should be set.

#include "ImageUtils.h" 
#include "HAL/FileManagerGeneric.h" 
#include "Engine/TextureRenderTarget2D.h" 
#include "Engine/CanvasRenderTarget2D.h" 
#include "Kismet/GameplayStatics.h" 
#include "Misc/LocalTimestampDirectoryVisitor.h" 
#include "Kismet/KismetRenderingLibrary.h"
#include "Engine/Canvas.h" 
#include "CanvasTypes.h" 
#include "TextureResource.h" 
#include "RendererInterface.h" 
#include "RHICommandList.h" 
#include "AssetRegistry/AssetRegistryModule.h" 
#include "Misc/Char.h" 


#include "FileHelpers.h" 

FString UImageFunctionLibrary::filterDirectory(FString &path)
{	
	if (path.Len() > 0) {
		path.ReplaceInline(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive);
		path.ReplaceInline(TEXT("//"), TEXT("/"), ESearchCase::CaseSensitive);
		path.RemoveFromStart(TEXT("/"));
		path.RemoveFromEnd(TEXT("/"));
		FPlatformMisc::NormalizePath(path);
		path.RemoveFromEnd(FPaths::GetExtension(path, true));
	}
	return path;
}


TArray<FString> UImageFunctionLibrary::GetAllFilesInDirectory(const FString directory, const bool fullPath, const FString onlyFilesStartingWith, const FString onlyFilesWithExtension,bool skipRecursionSelf = true)
{
	FString directoryCut = directory;
	filterDirectory(directoryCut);

	// Get all files in directory
	TArray<FString> directoriesToSkip;
	TArray<FString> directoriesToSkipRecurse;
	if (skipRecursionSelf == true) {
		directoriesToSkipRecurse.Add(directoryCut);
	}
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	FLocalTimestampDirectoryVisitor Visitor(PlatformFile, directoriesToSkip, directoriesToSkipRecurse, false);
	PlatformFile.IterateDirectory(*directoryCut, Visitor);
	TArray<FString> files;

	for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt)
	{
		const FString filePath = TimestampIt.Key();
		const FString fileName = FPaths::GetCleanFilename(filePath);
		bool shouldAddFile = true;

		// Check if filename starts with required characters
		if (!onlyFilesStartingWith.IsEmpty())
		{
			const FString left = fileName.Left(onlyFilesStartingWith.Len());

			if (!(fileName.Left(onlyFilesStartingWith.Len()).Equals(onlyFilesStartingWith)))
				shouldAddFile = false;
		}

		// Check if file extension is required characters
		if (!onlyFilesWithExtension.IsEmpty())
		{
			if (!(FPaths::GetExtension(fileName, false).Equals(onlyFilesWithExtension, ESearchCase::IgnoreCase)))
				shouldAddFile = false;
		}

		// Add full path to results
		if (shouldAddFile)
		{
			files.Add(fullPath ? filePath : fileName);
		}
	}

	return files;
}


I changed my code to this:

// Copyright (c) 2023 Neil Aggarwal, all rights reserved

#include "PuzzleCategories.h"

/* Function to get the puzzle categories */
TArray<FString>	UPuzzleCategories::getPuzzleCategories()
{
    if (puzzleCategories.IsEmpty() ) {
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		TArray<FString> directoriesToSkip;
		TArray<FString> directoriesToSkipRecurse;
		FLocalTimestampDirectoryVisitor Visitor(PlatformFile, directoriesToSkip, directoriesToSkipRecurse, true, false);
		PlatformFile.IterateDirectory(*FPaths::ProjectContentDir().Append(TEXT("Puzzles")), Visitor);

		for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt) {
			const FString filePath = TimestampIt.Key();
			const FString fileName = FPaths::GetCleanFilename(filePath);
			puzzleCategories.Add(fileName);
		}
    }
    return puzzleCategories;
}

And it compiles.

But, I am still getting the error in my blueprint that the get puzzle categories needs a target.


It works in my project.

Your UPuzzleCategories is of type actor or scene component hence the U prefix. You need to pass in the puzzle component that is added to your actor component.

Or just make it an actor component then there will be no target, it’ll take self as a basis.

How did you add the Puzzle Categories target showing in the graph?
blueprint

I made UPuzzleCategories derived from UActorComponent and added it through the add component panel of the actor blueprint. (You can see it in the first screenshot just below defaultSceneRoot of the actor)

Seems like you don’t need a world/object/actor ref so just make your function static so that it can be called anywhere without Target.

The proper base class would be UBlueprintFunctionLibrary, but UObject should work just fine too.

I changed my header to this:

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Misc/LocalTimestampDirectoryVisitor.h"
#include "PuzzleCategories.generated.h"

UCLASS()
class SLITHERLINK3D_API UPuzzleCategories : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "Puzzles")
		static TArray<FString> getPuzzleCategories();

private:
	static TArray<FString> puzzleCategories;
};

and my cpp file to this:

#include "PuzzleCategories.h"

TArray<FString> UPuzzleCategories::getPuzzleCategories()
{
    if (puzzleCategories.IsEmpty() ) {
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		TArray<FString> directoriesToSkip;
		TArray<FString> directoriesToSkipRecurse;
		FLocalTimestampDirectoryVisitor Visitor(PlatformFile, directoriesToSkip, directoriesToSkipRecurse, true, false);
		PlatformFile.IterateDirectory(*FPaths::ProjectContentDir().Append(TEXT("Puzzles")), Visitor);

		for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt) {
			const FString filePath = TimestampIt.Key();
			const FString fileName = FPaths::GetCleanFilename(filePath);
			puzzleCategories.Add(fileName);
		}
    }
    return puzzleCategories;
}

Now, when I try to do Build Solution in Visual Studio, I get this error:

2>   Creating library C:\Dev\Slitherlink3D\Intermediate\Build\Win64\x64\UnrealEditor\Development\Slitherlink3D\UnrealEditor-Slitherlink3D.suppressed.lib and object C:\Dev\Slitherlink3D\Intermediate\Build\Win64\x64\UnrealEditor\Development\Slitherlink3D\UnrealEditor-Slitherlink3D.suppressed.exp
2>PuzzleCategories.cpp.obj : error LNK2001: unresolved external symbol "private: static class TArray<class FString,class TSizedDefaultAllocator<32> > UPuzzleCategories::puzzleCategories" (?puzzleCategories@UPuzzleCategories@@0V?$TArray@VFString@@V?$TSizedDefaultAllocator@$0CA@@@@@A)
2>C:\Dev\Slitherlink3D\Binaries\Win64\UnrealEditor-Slitherlink3D.dll : fatal error LNK1120: 1 unresolved externals

I am not sure what that means. Any help?

You need to init the static TArray in a blueprint library.
I’ll look through my old scripts for an example. I need to refresh my memory :stuck_out_tongue:

ok here is the proper init for the static array in the cpp

// Fill out your copyright notice in the Description page of Project Settings.
#include "PuzzleCategories.h"


TArray<FString> UPuzzleCategories::puzzleCategories = TArray<FString>();

TArray<FString> UPuzzleCategories::getPuzzleCategories()
{
	if (puzzleCategories.IsEmpty()) {
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		TArray<FString> directoriesToSkip;
		TArray<FString> directoriesToSkipRecurse;
		FLocalTimestampDirectoryVisitor Visitor(PlatformFile, directoriesToSkip, directoriesToSkipRecurse, true, false);
		PlatformFile.IterateDirectory(*FPaths::ProjectContentDir().Append(TEXT("Puzzles")), Visitor);

		for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt) {
			const FString filePath = TimestampIt.Key();
			const FString fileName = FPaths::GetCleanFilename(filePath);
			puzzleCategories.Add(fileName);
		}
	}
	return puzzleCategories;
}

The init is not in any function because there is no CDO, begin play etc because the class is based on static functions just

TArray<FString> UPuzzleCategories::puzzleCategories = TArray<FString>();

Ahh. Got it. Thank you!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.