Download

"Error LKN2019: unresolved external symbols" with data asset classes from the StateMachine plugin

I am trying to build the StateMachine plugin from the Unreal live training.

https://www.youtube.com/watch?v=hr9ybCCPw9Y&list=PLoSx0HdYYfVy-qz4mljSWUjkZrrFiFVTy&index=1

The training is a bit dated but through trial and error I got the plugin to compile and am able to create the quest data assets in the plugin development project. The problem I can’t solve is unresolved external symbols in the QuestStatus class in the project. As you can see I included SM_State.h in the class and StateMachine in the build.cs. A bit stumped as to why this is happening other than it seems the data asset classes in SM_State.h are not accessible to project C++ classes. I tried moving the unresolved classes in the plugin to a new data asset class in the project and the errors go away. But that defeats the purpose of the plugin.

The plugin classes are created under the plugin’s Public folder, not the Classes folder as they did in the tutorial. I had tried Classes in another project and it got the same error. Any help would be appreciated.

QuestStatus.gen.cpp.obj : error LNK2019: unresolved external symbol “__declspec(dllimport) class UClass * __cdecl Z_Construct_UClass_USM_InputAtom_NoRegister(void)”
QuestStatus.gen.cpp.obj : error LNK2019: unresolved external symbol “__declspec(dllimport) class UClass * __cdecl Z_Construct_UClass_USM_State_NoRegister(void)”
[HR][/HR]
PluginDevelopment.build.cs


using UnrealBuildTool;

public class PluginDevelopment : ModuleRules
{
public PluginDevelopment(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

PublicDependencyModuleNames.AddRange(new string] { "Core", "CoreUObject", "Engine", "InputCore", "StateMachine" });
}
}

QuestStatus.h


// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Components/ActorComponent.h"
#include "SM_State.h"
#include "QuestStatus.generated.h"

UENUM()
enum class EQuestCompletion : uint8
{
EQC_NotStarted,
EQC_Started,
EQC_Succeeded,
EQC_Failed
};

UCLASS()
class PLUGINDEVELOPMENT_API UQuest : public UDataAsset
{
GENERATED_BODY()

public:
// The name of the quest.
UPROPERTY(EditAnywhere)
FText QuestName;

// If this machine accepts out QuestActivities log, the quest is successful.
UPROPERTY(EditAnywhere)
USM_State* QuestStateMachine; // <<<< THIS LINE GENERATES ERROR LNK2019: UNRESOLVED EXTERNAL SYMBOLS

// If true the InputList is a blacklist. Otherwise it's a whitelist.
UPROPERTY(EditAnywhere)
uint32 bInputBlackList : 1;

// The blacklist/whitelist (depending on bInputBlackList) used to filter InputAtoms this Quest recognizes.
UPROPERTY(EditAnywhere)
TArray<USM_InputAtom*> InputList; // <<<< THIS LINE GENERATES ERROR LNK2019: UNRESOLVED EXTERNAL SYMBOLS
};

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class PLUGINDEVELOPMENT_API UQuestStatus : public UActorComponent
{
GENERATED_BODY()

public:
// Sets default values for this component's properties
UQuestStatus();

protected:
// Called when the game starts
virtual void BeginPlay() override;

public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

};

SM_State.h


// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "SM_State.generated.h"

UENUM()
enum class EStateMachineCompletionType : uint8
{
// Implicit failure - this state is not marked as Accept.
NotAccepted,

// Success - this state is an Accept state.
Accepted,

// Explicit failure - this state is specifically marked as failure/not-accept.
Rejected,

// Our simulation ran out of steps while the machine was still running.
OutOfSteps UMETA(Hidden)
};

USTRUCT(BlueprintType)
struct STATEMACHINE_API FStateMachineResult
{
GENERATED_BODY()

UPROPERTY()
EStateMachineCompletionType CompletionType;

UPROPERTY()
USM_State* FinalState;

UPROPERTY()
int32 DataIndex;
};

UCLASS()
class STATEMACHINE_API USM_InputAtom : public UDataAsset
{
GENERATED_BODY()

public:
// Display value for this input atom, mainly for debugging purposes.
UPROPERTY(EditAnywhere)
FName Description;
};

UCLASS(EditInlineNew)
class STATEMACHINE_API USM_Branch : public UDataAsset
{
GENERATED_BODY()

public:
/** Returns DestinationState on success, NULL on failure. For subclasses,
OutDataIndex might be something other than 1, if a branch is made to consume multiple inputs. */
virtual USM_State* TryBranch(const UObject* RefObject, const TArray<USM_InputAtom*>& DataSource, int32 DataIndex, int32& OutDataIndex);

private:
// State where we will go next if this branch is taken. If null this branch will be ignored.
UPROPERTY(EditAnywhere)
USM_State* DestinationState;

// If true the meaning of acceptable inputs is reversed.
UPROPERTY(EditAnywhere)
uint32 bReverseInputTest : 1;

// Acceptable inputs. The current input atom must be on this list.
UPROPERTY(EditAnywhere)
TArray<USM_InputAtom*> AcceptableInputs;
};

UCLASS()
class STATEMACHINE_API USM_State : public UDataAsset
{
GENERATED_BODY()

public:

USM_State();

/** Attempt to run the state with the input from the given DataSource object.
Will start reading input at DataIndex.
Will decrement RemainingSteps and automatically fail after it hits 0. */

UFUNCTION(BlueprintCallable, Category = "State Machine")
virtual FStateMachineResult RunState(const UObject* RefObject, const TArray<USM_InputAtom*>& DataSource, int32 DataIndex = 0, int32 RemainingSteps = -1);

protected:

// Loop. Used when input atom is being processed isn't recognized.
virtual FStateMachineResult LoopState(const UObject* RefObject, const TArray<USM_InputAtom*>& DataSource, int32 DataIndex, int32 RemainingSteps);

// If input runs out on this state (or TerminateImmediately is true), this is how the result will be interpreted.
UPROPERTY(EditAnywhere)
EStateMachineCompletionType CompletionType;

// A state machine run that enters this state will terminate immediately, regardless of whether or not there is more input data.
UPROPERTY(EditAnywhere)
uint32 bTerminateImmediately : 1;

//If this is set this state will loop on itself whenever an unhandled input atom is detected.
UPROPERTY(EditAnywhere)
uint32 bLoopByDefault : 1;

// Branches to other states. These are in priority order, so the first successful branch will be taken.
UPROPERTY(EditAnywhere, Instanced)
TArray<USM_Branch*> InstancedBranches;

};