UE5.6.1: Implement a custom Mover and custom MoverExamplesCharacter in pure C++ (blueprints not required.)

Everything worked out-of-the-box for the MoverExamples w/r to the example blueprint Pawns.

However, there are component initialization issues when implementing with a pure C++ Pawn.

Notice:

My project uses StateTree AI and the CapsuleComponent moved as instructed.

Just swapped the CharacterMotionComponent tech with the MoverComponent tech.

I have AIPawns that do not require the Character stuff.

Currently learning and refactoring my project from AICharacter/CharacterMotionComponent to AIPawn/MoverComponent tech.

The values from the Plugins\MoverExamples\Pawns\PathFollowingMannyPawn blueprint were used as a baseline.

The StateTree AI was implemented with a combination of blueprint StateTrees and C++ tasks but the default AIMove_To was utilized.

I assume this will also work with BehaviorTrees if the default AIMove_To is used?

Setup:

Backup project just in case.

Requires the ChaosMover plugin to be copied into project Plugins folder in addition to Mover and MoverExamples:

Copy all three plugins to {project_name}\Plugins dir.

Ensure the following is in {project_name}.Build.cs:

PublicDependencyModuleNames.AddRange(new string[] { , "ChaosMover", "Mover", "MoverExamples",  });

Ensure the following is in {project_name}.uproject:

	"Modules": [
		{
			
			"AdditionalDependencies": [
			
			"Mover",
			
			]
		},
	"Plugins": [
		
		{
			"Name": "ChaosMover",
			"Enabled": false
		},
		{
			"Name": "Mover",
			"Enabled": true
		},
		{
			"Name": "MoverExamples",
			"Enabled": true
		}
		
		]

Note that ChaosMover is not enabled so enable it if you need it.

The ChaosMover requirement is probably a consequence of Experimental code but is needed to compile.

Alt-click {project_name}.uproject file and Generate Visual Studio project files (may have to select Show more options first) to update project.

Sometimes the Binaries/Intermediate/DerivedDataCache/Saved folders can interfere with the “update project” and/or “compile” commands.

They can be deleted (backup first) and will regenerate.

In CustomMoverComponent.h:

#pragma once

#include "CoreMinimal.h"
#include "DefaultMovementSet/CharacterMoverComponent.h"
#include "CustomMoverComponent.generated.h"

UCLASS()
class {project_name}_API UCustomMoverComponent : public UCharacterMoverComponent
{
	GENERATED_BODY()
	
public:
	// Constructor.
	UCustomMoverComponent();

	// Methods.
	virtual void OnRegister() override;
};

In CustomMoverComponent.cpp:

#include "CustomMoverComponent.h"
#include "Backends/MoverStandaloneLiaison.h"

UCustomMoverComponent::UCustomMoverComponent()
{
	// Change default MoverComponent constructor settings here.
	
	// I did not need chaos physics or network stuff but the default worked too.
	BackendClass = UMoverStandaloneLiaisonComponent::StaticClass();
}

void UCustomMoverComponent::OnRegister()
{
	Super::OnRegister();

	// REQUIRED: Otherwise, CommonLegacyMovementSettings does not get initialized prior to the MovementMode objects.
	RefreshSharedSettings();
}

In CustomNavMoverComponent.h:

#pragma once

#include "CoreMinimal.h"
#include "CustomNavMoverComponent.generated.h"
#include "DefaultMovementSet/NavMoverComponent.h"

UCLASS()
class NEURAPTOBOT_API UCustomNavMoverComponent : public UNavMoverComponent
{
	GENERATED_BODY()

protected:
	// Methods.
	virtual void BeginPlay() override;
};

In CustomNavMoverComponent.cpp:

#include "CustomNavMoverComponent.h"

// The only method that I found for updating properties AFTER CustomPawn is spawned.
void UCustomNavMoverComponent::BeginPlay()
{
	Super::BeginPlay();

	// Set properties based on the Plugins\\MoverExamples\\Pawns\\PathFollowingMannyPawn blueprint.
	MovementState.bCanCrouch = false;
	MovementState.bCanSwim = false;
	NavMovementProperties.bStopMovementAbortPaths = true;
	NavMovementProperties.bUpdateNavAgentWithOwnersCollision = true;
	SetComponentTickEnabled(true);
}

In CustomPawn.h:

#include "CustomMoverComponent.h"

class UCustomNavMoverComponent;

// REQUIRED: IMoverInputProducerInterface.
UCLASS()
class NEURAPTOBOT_API ACustomPawn : public APawn, public IMoverInputProducerInterface
{
	GENERATED_BODY()


	// Copy/merge/implement all properties/methods from MoverExamplesCharacter.h

	// Notes: Can skip implementing the following for pure C++ AI Pawns but it can also be included with no real impact.
	
	/*
	// If not using blueprints then do not need.
	uint8 bHasProduceInputinBpFunc : 1;
	*/

	/*
	// If not using blueprints then do not need.
	// Implement this event in Blueprints to author input for the next simulation frame. Consider also calling Parent event.
	UFUNCTION(BlueprintImplementableEvent, DisplayName = "On Produce Input", meta = (ScriptName = "OnProduceInput"))
	FMoverInputCmdContext OnProduceInputInBlueprint(float DeltaMs, FMoverInputCmdContext InputCmd);
	*/

	/*
	// If only using AI controller then no need to implement Tick or SetupPlayerInputComponent.
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	*/
	
protected:

	// REQUIRED: the CustomMoverComponent must be updated.
	// I use pooling so this is all that I tested.
	// PostInitializeComponents, BeginPlay, etc. that execute ONCE, AFTER the Mover components have initialized, should work?
	void OnSpawnFromPool();
	
	virtual void PostInitializeComponents() override;
};

In CustomPawn.cpp:

// Copy/merge/implement all methods from MoverExamplesCharacter.h
	
// IMPORTANT: Search/Replace all "CharacterMotionComponent" with "CustomMoverComponent".

#include "CustomPawn.h"
#include "DefaultMovementSet/Settings/CommonLegacyMovementSettings.h"
#include "CustomNavMoverComponent.h"
#include "EnhancedInputComponent.h"

ACustomPawn::ACustomPawn(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	CustomMoverComponent = ObjectInitializer.CreateDefaultSubobject<UCustomMoverComponent>(this, TEXT("CustomMoverComponent"));
	
	// There are direct references to "NavMoverComponent", so leave the label alone.
	// Actually CreateDefaultSubobject of the new CustomNavMoverComponent, though.
	NavMoverComponent = ObjectInitializer.CreateDefaultSubobject<UCustomNavMoverComponent>(this, TEXT("NavMoverComponent"));

	// Note: Mover tech no longer requires CapsuleComponent but the MoverExamplesCharacter code assumes CapsuleComponent.
	RootComponent = CapsuleComponent;

	// Disable Actor-level movement replication, since our Mover component will handle it.
	SetReplicatingMovement(false);	

	/*
	// If not using blueprints then do not need.
	auto IsImplementedInBlueprint = \[\](const UFunction* Func) -> bool
		{
			return Func && ensure(Func->GetOuter())
				&& Func->GetOuter()->IsA(UBlueprintGeneratedClass::StaticClass());
		};

	static FName ProduceInputBPFuncName = FName(TEXT("OnProduceInputInBlueprint"));
	UFunction* ProduceInputFunction = GetClass()->FindFunctionByName(ProduceInputBPFuncName);
	bHasProduceInputinBpFunc = IsImplementedInBlueprint(ProduceInputFunction);
	*/
}

void ACustomPawn::OnSpawnFromPool()
{
	// REQUIRED: the CustomMoverComponent must be updated.
	// Note: I added the assigned vars to the header but they can be hardwired if no differentiation of pawns is needed. 
	// The assigned vars were initially based on the Plugins\\MoverExamples\\Pawns\\PathFollowingMannyPawn blueprint.
	if (CustomMoverComponent)
	{
		CustomMoverComponent->SetGravityOverride(bGravityOverride, GravityAccelOverride);
		CustomMoverComponent->SetHandleJump(bCanJump);
		CustomMoverComponent->SetHandleStanceChanges(bCanChangeStance);

		if (UCommonLegacyMovementSettings* CommonSettings = CustomMoverComponent->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
		{
			CommonSettings->Acceleration = Acceleration;
			CommonSettings->MaxSpeed = MaxWalkSpeedDefault;
			CommonSettings->BrakingFriction = BrakingFriction;
			CommonSettings->BrakingFrictionFactor = BrakingFrictionFactor;
			CommonSettings->bShouldRemainVertical = bShouldRemainVertical;
			CommonSettings->bUseAccelerationForVelocityMove = bUseAccelerationForVelocityMove;
			CommonSettings->bUseSeparateBrakingFriction = bUseSeparateBrakingFriction;
			CommonSettings->Deceleration = Deceleration;
			CommonSettings->FloorSweepDistance = FloorSweepDistance;
			CommonSettings->GroundFriction = GroundFriction;
			CommonSettings->JumpUpwardsSpeed = JumpUpwardsSpeed;
			CommonSettings->MaxStepHeight = MaxStepHeight;
			CommonSettings->MaxWalkSlopeCosine = MaxWalkSlopeCosine;
			CommonSettings->TurningBoost = TurningBoost;
			CommonSettings->TurningRate = TurningRate;
		}
	}

	// NOT Required: the CustomNavMoverComponent can update itself in CustomNavMoverComponent::BeginPlay.
	// Invariant properties should be set in CustomNavMoverComponent::BeginPlay.
	// The assigned vars were initially based on the Plugins\\MoverExamples\\Pawns\\PathFollowingMannyPawn blueprint.
	if (NavMoverComponent)
	{
		NavMoverComponent->MovementState.bCanJump = bCanJump;
		NavMoverComponent->MovementState.bCanWalk = bCanWalk;
		NavMoverComponent->NavAgentProps.AgentHeight = AgentHeight;
		NavMoverComponent->NavAgentProps.AgentRadius = AgentRadius;
		NavMoverComponent->NavAgentProps.AgentStepHeight = AgentStepHeight;
		NavMoverComponent->NavAgentProps.NavWalkingSearchHeightScale = NavWalkingSearchHeightScale;
	}
}

void ACustomPawn::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	// Check CustomMoverComponent and CapsuleComponent.
	if (CustomMoverComponent && CapsuleComponent)
	{
		// SetUpdatedComponent.
		CustomMoverComponent->SetUpdatedComponent(CapsuleComponent);

		// Check UpdatedComponent.
		if (USceneComponent* UpdatedComponent = CustomMoverComponent->GetUpdatedComponent())
		{
			// The assigned vars were initially based on the Plugins\\MoverExamples\\Pawns\\PathFollowingMannyPawn blueprint.
			// SetCanEverAffectNavigation.
			UpdatedComponent->SetCanEverAffectNavigation(bCanAffectNavigationGeneration);
		}
	}
}