Fix character rotation in editor + defer timeline play until AI ready

- Save pawn rotation/movement settings (bUseControllerRotation*, bOrient-
  RotationToMovement, MovementMode) on SpawnableComponent when editor
  disables AI, then restore them exactly in StartSimulation
- Re-apply rotation/movement overrides on StopSimulation so the character
  doesn't auto-rotate after returning from Simulate
- Poll spawned pawns' BrainComponent on scene load (gameplay via
  SceneLoader); start timeline playback only once every AI BT is running.
  Fixes timeline keys firing before the AI-driven sequence (walk) starts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 18:27:42 +02:00
parent d9d358e694
commit b069c20af3
5 changed files with 164 additions and 3 deletions

View File

@@ -10,6 +10,9 @@
#include "PS_Editor_SplineEditable.h"
#include "PS_Editor_PointPlaceable.h"
#include "Engine/LevelStreamingDynamic.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "PS_Editor_SpawnableComponent.h"
#include "Engine/PostProcessVolume.h"
#include "PS_Editor_CameraStart.h"
#include "PS_Editor_ChildMovable.h"
@@ -256,7 +259,7 @@ void APS_Editor_PlayerController::StartSimulation()
SelectionManager->ClearSelection();
}
// Enable AI logic on all spawned pawns
// Enable AI logic on all spawned pawns and restore movement settings from class defaults
if (SpawnManager)
{
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
@@ -264,6 +267,25 @@ void APS_Editor_PlayerController::StartSimulation()
if (APawn* P = Cast<APawn>(Weak.Get()))
{
P->SetActorTickEnabled(true);
// Restore rotation/movement settings saved when AI was disabled
UPS_Editor_SpawnableComponent* SaveComp = P->FindComponentByClass<UPS_Editor_SpawnableComponent>();
if (SaveComp && SaveComp->bSavedMovementState)
{
P->bUseControllerRotationYaw = SaveComp->bSavedUseControllerRotationYaw;
P->bUseControllerRotationPitch = SaveComp->bSavedUseControllerRotationPitch;
P->bUseControllerRotationRoll = SaveComp->bSavedUseControllerRotationRoll;
if (ACharacter* Char = Cast<ACharacter>(P))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
Move->bOrientRotationToMovement = SaveComp->bSavedOrientRotationToMovement;
Move->bUseControllerDesiredRotation = SaveComp->bSavedUseControllerDesiredRotation;
Move->SetMovementMode(SaveComp->SavedMovementMode, SaveComp->SavedCustomMovementMode);
}
}
}
if (AAIController* AIC = Cast<AAIController>(P->GetController()))
{
AIC->SetActorTickEnabled(true);
@@ -315,7 +337,7 @@ void APS_Editor_PlayerController::StopSimulation()
if (!bSimulating) return;
bSimulating = false;
// Disable AI logic on all spawned pawns
// Disable AI logic and re-apply rotation/movement overrides on all spawned pawns
if (SpawnManager)
{
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
@@ -331,6 +353,20 @@ void APS_Editor_PlayerController::StopSimulation()
Brain->StopLogic(TEXT("PS_Editor"));
}
}
// Re-apply editor overrides (values were already saved at first disable)
if (ACharacter* Char = Cast<ACharacter>(P))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
Move->bOrientRotationToMovement = false;
Move->bUseControllerDesiredRotation = false;
Move->SetMovementMode(MOVE_None);
}
}
P->bUseControllerRotationYaw = false;
P->bUseControllerRotationPitch = false;
P->bUseControllerRotationRoll = false;
}
}
}

View File

@@ -10,6 +10,8 @@
#include "PS_Editor_TimelineSubsystem.h"
#include "PS_Editor_SpawnManager.h"
#include "PS_Editor_PlayerController.h"
#include "AIController.h"
#include "BrainComponent.h"
#include "JsonObjectConverter.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
@@ -160,7 +162,47 @@ bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_S
TimelineSS->SetData(SceneData.Timeline);
if (!IsInEditorMode() && SceneData.Timeline.Tracks.Num() > 0)
{
TimelineSS->Play();
// Start timeline only when all spawned pawns have their BT running.
// Poll each tick until ready to avoid firing keys before AI-driven sequences start.
TWeakObjectPtr<UPS_Editor_TimelineSubsystem> WeakTS = TimelineSS;
TArray<TWeakObjectPtr<APawn>> WeakPawns;
for (AActor* A : OutActors)
{
if (APawn* P = Cast<APawn>(A)) WeakPawns.Add(P);
}
TWeakObjectPtr<UWorld> WeakWorld = World;
TSharedPtr<FTimerHandle> PollTimerPtr = MakeShared<FTimerHandle>();
TSharedPtr<int32> PollCount = MakeShared<int32>(0);
World->GetTimerManager().SetTimer(*PollTimerPtr, [WeakTS, WeakPawns, WeakWorld, PollTimerPtr, PollCount]()
{
(*PollCount)++;
// Check all pawns have an AIController with a running BrainComponent
bool bAllReady = true;
for (const TWeakObjectPtr<APawn>& WeakP : WeakPawns)
{
APawn* P = WeakP.Get();
if (!P) continue;
AAIController* AIC = Cast<AAIController>(P->GetController());
if (!AIC) { bAllReady = false; continue; }
UBrainComponent* Brain = AIC->GetBrainComponent();
if (!Brain || !Brain->IsRunning()) { bAllReady = false; continue; }
}
// Wait for all pawns AND at least 2 poll cycles to ensure BT has started its first task
if (bAllReady && *PollCount >= 2)
{
if (UPS_Editor_TimelineSubsystem* TS = WeakTS.Get())
{
TS->Play();
}
if (UWorld* W = WeakWorld.Get())
{
W->GetTimerManager().ClearTimer(*PollTimerPtr);
}
}
}, 0.1f, true); // poll every 100ms
}
}

View File

@@ -10,6 +10,8 @@
#include "PS_Editor_SplineActor.h"
#include "AIController.h"
#include "BrainComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "PS_Editor_SplineEditable.h"
#include "PS_Editor_TimelineSubsystem.h"
#include "JsonObjectConverter.h"
@@ -348,6 +350,39 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
Brain->StopLogic(TEXT("PS_Editor"));
}
}
// Save original movement/rotation state on SpawnableComponent, then override
UPS_Editor_SpawnableComponent* SaveComp = Pawn->FindComponentByClass<UPS_Editor_SpawnableComponent>();
if (SaveComp && !SaveComp->bSavedMovementState)
{
SaveComp->bSavedUseControllerRotationYaw = Pawn->bUseControllerRotationYaw;
SaveComp->bSavedUseControllerRotationPitch = Pawn->bUseControllerRotationPitch;
SaveComp->bSavedUseControllerRotationRoll = Pawn->bUseControllerRotationRoll;
if (ACharacter* Char = Cast<ACharacter>(Pawn))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
SaveComp->bSavedOrientRotationToMovement = Move->bOrientRotationToMovement;
SaveComp->bSavedUseControllerDesiredRotation = Move->bUseControllerDesiredRotation;
SaveComp->SavedMovementMode = Move->MovementMode;
SaveComp->SavedCustomMovementMode = Move->CustomMovementMode;
}
}
SaveComp->bSavedMovementState = true;
}
if (ACharacter* Char = Cast<ACharacter>(Pawn))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
Move->bOrientRotationToMovement = false;
Move->bUseControllerDesiredRotation = false;
Move->SetMovementMode(MOVE_None);
}
}
Pawn->bUseControllerRotationYaw = false;
Pawn->bUseControllerRotationPitch = false;
Pawn->bUseControllerRotationRoll = false;
};
DisableAI(P);

View File

@@ -8,6 +8,8 @@
#include "AIController.h"
#include "BrainComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Engine/World.h"
namespace
@@ -209,6 +211,40 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
Brain->StopLogic(TEXT("PS_Editor"));
}
}
// Save original movement/rotation state on SpawnableComponent, then override
UPS_Editor_SpawnableComponent* SaveComp = Pawn->FindComponentByClass<UPS_Editor_SpawnableComponent>();
if (SaveComp && !SaveComp->bSavedMovementState)
{
SaveComp->bSavedUseControllerRotationYaw = Pawn->bUseControllerRotationYaw;
SaveComp->bSavedUseControllerRotationPitch = Pawn->bUseControllerRotationPitch;
SaveComp->bSavedUseControllerRotationRoll = Pawn->bUseControllerRotationRoll;
if (ACharacter* Char = Cast<ACharacter>(Pawn))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
SaveComp->bSavedOrientRotationToMovement = Move->bOrientRotationToMovement;
SaveComp->bSavedUseControllerDesiredRotation = Move->bUseControllerDesiredRotation;
SaveComp->SavedMovementMode = Move->MovementMode;
SaveComp->SavedCustomMovementMode = Move->CustomMovementMode;
}
}
SaveComp->bSavedMovementState = true;
}
// Override to prevent auto-rotation in editor
if (ACharacter* Char = Cast<ACharacter>(Pawn))
{
if (UCharacterMovementComponent* Move = Char->GetCharacterMovement())
{
Move->bOrientRotationToMovement = false;
Move->bUseControllerDesiredRotation = false;
Move->SetMovementMode(MOVE_None);
}
}
Pawn->bUseControllerRotationYaw = false;
Pawn->bUseControllerRotationPitch = false;
Pawn->bUseControllerRotationRoll = false;
};
DisableAI(SpawnedPawn);

View File

@@ -2,6 +2,7 @@
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Engine/EngineTypes.h"
#include "PS_Editor_SpawnableComponent.generated.h"
/**
@@ -52,4 +53,15 @@ public:
* Use scene loading filters to load only preload actors or exclude them. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable")
bool bPreload = false;
// ---- Runtime-only: saved movement/rotation settings before editor override ----
// Set when the editor disables AI/rotation overrides, restored on StartSimulation.
bool bSavedMovementState = false;
bool bSavedUseControllerRotationYaw = false;
bool bSavedUseControllerRotationPitch = false;
bool bSavedUseControllerRotationRoll = false;
bool bSavedOrientRotationToMovement = false;
bool bSavedUseControllerDesiredRotation = false;
TEnumAsByte<EMovementMode> SavedMovementMode = MOVE_Walking;
uint8 SavedCustomMovementMode = 0;
};