Hello here.
I ask for a bit help since i lost my night on this bug.
This is my situation : I have a functionnal testing file, who simulate input in my PlayerController to test movement functions.
Here an example :
// Inventory.spec.cpp
#include "InputActionValue.h"
#include "Components/CapsuleComponent.h"
#include "Misc/AutomationTest.h"
#include "santa_marias_echoes/Tests/Helpers/TestWorldHelper.h"
#include "Slate/SceneViewport.h"
#include "Widgets/SViewport.h"
BEGIN_DEFINE_SPEC(FSMEPlayerControllerSpec,
"Santa_Maria_Echoes.PlayerController.SMEPlayerController",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags::EditorContext)
TArray<int32> Items;
END_DEFINE_SPEC(FSMEPlayerControllerSpec)
void FSMEPlayerControllerSpec::Define()
{
It("Should move player if PC StrafePress method called (Strafe Left and Right)", [this]()
{
//--------------------------------------------------------------------
// Given
//--------------------------------------------------------------------
FTestWorldWrapper TW = FTestWorldHelper::InitTestWorld(this);
ASMEGameMode* GM = FTestWorldHelper::GetGameMode(TW);
FTestWorldHelper::SpawnTestFloor(TW);
FTestWorldHelper::TickForSeconds(TW, 10);
GM->SetMatchState(MatchState::InProgress);
ASMEPlayerController* PC = Cast<ASMEPlayerController>(GM->SpawnPlayerController(ROLE_Authority, ""));
TestTrue(TEXT("PlayerController is ATestSMEPlayerController"), PC != nullptr);
if (!PC) return false;
PC->InitPlayerState();
GM->RestartPlayerAtTransform(PC, FTransform(FVector(0, 0, 100)));
FTestWorldHelper::TickForSeconds(TW, 10);
TestTrue("Pawn is possessed", PC->GetPawn() != nullptr);
if (!PC->GetPawn()) return false;
ASMECharacter* CHARACTER = Cast<ASMECharacter>(PC->GetPawn());
TestTrue(TEXT("Character is ASMECharacter"), CHARACTER != nullptr);
if (!CHARACTER) return false;
FTestWorldHelper::TickForSeconds(TW, 60);
TestTrue("Character have a PlayerController", CHARACTER->GetController() != nullptr);
if (!CHARACTER->GetController()) return false;
TestTrue("Character have a PlayerController of type ASMEPlayerController", CHARACTER->GetController()->IsA(ASMEPlayerController::StaticClass()));
const FVector InitialLocation = CHARACTER->GetActorLocation();
//--------------------------------------------------------------------
// When
//--------------------------------------------------------------------
FInputActionValue ActionValue(1.f);
PC->StrafePressed(ActionValue);
FTestWorldHelper::TickForSeconds(TW, 60);
const FVector AfterRightLocation = CHARACTER->GetActorLocation();
ActionValue = FInputActionValue(-1.f);
PC->StrafePressed(ActionValue);
FTestWorldHelper::TickForSeconds(TW, 60);
const FVector AfterLeftLocation = CHARACTER->GetActorLocation();
//--------------------------------------------------------------------
// Then
//--------------------------------------------------------------------
TestTrue(TEXT("Character has moved right when StrafePressed with value 1"), AfterRightLocation.Y > InitialLocation.Y);
TestTrue(TEXT("Character has moved left when StrafePressed with value -1"), AfterLeftLocation.Y < AfterRightLocation.Y);
return true;
});
}
Here the FTestWorldHelper :
#pragma once
#include "Components/BoxComponent.h"
#include "santa_marias_echoes/Core/Character/SMECharacter.h"
#include "santa_marias_echoes/Core/GameMode/SMEGameMode.h"
#include "santa_marias_echoes/Core/GameState/SMEGameState.h"
#include "santa_marias_echoes/Core/PlayerController/SMEPlayerController.h"
#include "santa_marias_echoes/Core/PlayerState/SMEPlayerState.h"
#include "santa_marias_echoes/Menu/SMEGameHud.h"
#include "Misc/AutomationTest.h"
#include "santa_marias_echoes/HUD/SMEHUD.h"
#include "Tests/AutomationCommon.h"
class FTestWorldHelper
{
public:
static void ApplyDefaultClassSettings(ASMEGameMode* GameMode)
{
GameMode->DefaultPawnClass = ASMECharacter::StaticClass();
GameMode->PlayerControllerClass = ASMEPlayerController::StaticClass();
GameMode->HUDClass = ASMEHUD::StaticClass();
GameMode->PlayerStateClass = ASMEPlayerState::StaticClass();
GameMode->GameStateClass = ASMEGameState::StaticClass();
}
static FTestWorldWrapper InitTestWorld(FAutomationSpecBase* Spec, bool BeginPlay = true, TFunction<void(ASMEGameMode*)> OverideClassSettings = nullptr)
{
FTestWorldWrapper TW;
Spec->TestTrue(TEXT("CreateTestWorld(Game)"), TW.CreateTestWorld(EWorldType::Game));
UWorld* W = TW.GetTestWorld();
Spec->TestNotNull(TEXT("World valid"), W);
if (W->GetWorldSettings())
{
W->GetWorldSettings()->DefaultGameMode = ASMEGameMode::StaticClass();
}
bool bGMSet = (W->SetGameMode(FURL()));
Spec->TestTrue(TEXT("SetGameMode(ATestSMEGameMode)"), bGMSet);
if (BeginPlay)
{
Spec->TestTrue(TEXT("BeginPlayInTestWorld()"), TW.BeginPlayInTestWorld());
}
ASMEGameMode* GM = TW.GetTestWorld()->GetAuthGameMode<ASMEGameMode>();
ApplyDefaultClassSettings(GM);
if (OverideClassSettings) OverideClassSettings(GM);
Spec->TestTrue(TEXT("GameMode is ATestSMEGameMode"), GM != nullptr);
return TW;
}
static void TickForSeconds(FTestWorldWrapper& TW, int32 Ticks)
{
for (int i = 0; i < Ticks; ++i)
{
TW.TickTestWorld(1.f / 60.f);
}
}
static void TickForSeconds(FTestWorldWrapper& TW, int32 Ticks, TFunction<void()> PreTick)
{
for (int i = 0; i < Ticks; ++i)
{
PreTick();
TW.TickTestWorld(1.f / 60.f);
}
}
static ASMEGameMode* GetGameMode(FTestWorldWrapper& TW)
{
if (!TW.GetTestWorld()) return nullptr;
return TW.GetTestWorld()->GetAuthGameMode<ASMEGameMode>();
}
static void SpawnTestFloor(FTestWorldWrapper& TW)
{
UWorld* World = TW.GetTestWorld();
if (!World) return;
FActorSpawnParameters SpawnParams;
SpawnParams.Name = TEXT("TestFloor");
AActor* Floor = World->SpawnActor<AActor>(AActor::StaticClass(), FTransform(FVector(0.f, 0.f, -10.f)), SpawnParams);
// Crée une boîte collision statique qui bloque tout (dont Pawn)
UBoxComponent* Box = NewObject<UBoxComponent>(Floor, TEXT("FloorBox"));
Box->InitBoxExtent(FVector(5000,5000,10));
Box->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
Box->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
Box->SetGenerateOverlapEvents(false);
Box->SetMobility(EComponentMobility::Static);
Floor->SetRootComponent(Box);
Box->RegisterComponent(); // important pour que la collision existe
}
};
And finally this is my Character/Player Controller Move Function :
void ASMEPlayerController::StrafePressed(const FInputActionValue& InputActionValue)
{
if (SMECharacter)
{
SMECharacter->MoveRight(InputActionValue.Get<float>());
}
}
void ASMECharacter::MoveRight(float Value)
{
if (Controller != nullptr && Value != 0.f)
{
const FRotator YawRotation(0.f, Controller->GetControlRotation().Yaw, 0.f);
const FVector Direction(FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y));
AddMovementInput(Direction, Value);
}
}
In Unreal Engine 5.6 all work perfectly and all my test are successful.
When i have upgrade to 5.7 all test based on movement (CharMovement) not working anymore.
All check of position returns the same spawn position 0.0.100.
Obviously i have check all ptr/nullptr. Everything looks fine. I also debug and i don’t see anything bad.
The only track i have is the apperance of two new properties on UCharacterMovement class (bEnableScopedMovementUpdates & bEnableServerDualMoveScopedMovementUpdates)
( UCharacterMoverComponent | Unreal Engine 5.7 Documentation | Epic Developer Community )
which seems to refer a new system to handle movement & movement replication but i can’t confirm.
I will continue to investigate later this week but if anybody have a clue or solution i’ll take it ![]()