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);
\}
\}
...
}