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

Title:
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 so…
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
}

]

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 didn't 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);
	\}
\}

...

}