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:
@@ -10,6 +10,9 @@
|
|||||||
#include "PS_Editor_SplineEditable.h"
|
#include "PS_Editor_SplineEditable.h"
|
||||||
#include "PS_Editor_PointPlaceable.h"
|
#include "PS_Editor_PointPlaceable.h"
|
||||||
#include "Engine/LevelStreamingDynamic.h"
|
#include "Engine/LevelStreamingDynamic.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
|
#include "PS_Editor_SpawnableComponent.h"
|
||||||
#include "Engine/PostProcessVolume.h"
|
#include "Engine/PostProcessVolume.h"
|
||||||
#include "PS_Editor_CameraStart.h"
|
#include "PS_Editor_CameraStart.h"
|
||||||
#include "PS_Editor_ChildMovable.h"
|
#include "PS_Editor_ChildMovable.h"
|
||||||
@@ -256,7 +259,7 @@ void APS_Editor_PlayerController::StartSimulation()
|
|||||||
SelectionManager->ClearSelection();
|
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)
|
if (SpawnManager)
|
||||||
{
|
{
|
||||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||||
@@ -264,6 +267,25 @@ void APS_Editor_PlayerController::StartSimulation()
|
|||||||
if (APawn* P = Cast<APawn>(Weak.Get()))
|
if (APawn* P = Cast<APawn>(Weak.Get()))
|
||||||
{
|
{
|
||||||
P->SetActorTickEnabled(true);
|
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()))
|
if (AAIController* AIC = Cast<AAIController>(P->GetController()))
|
||||||
{
|
{
|
||||||
AIC->SetActorTickEnabled(true);
|
AIC->SetActorTickEnabled(true);
|
||||||
@@ -315,7 +337,7 @@ void APS_Editor_PlayerController::StopSimulation()
|
|||||||
if (!bSimulating) return;
|
if (!bSimulating) return;
|
||||||
bSimulating = false;
|
bSimulating = false;
|
||||||
|
|
||||||
// Disable AI logic on all spawned pawns
|
// Disable AI logic and re-apply rotation/movement overrides on all spawned pawns
|
||||||
if (SpawnManager)
|
if (SpawnManager)
|
||||||
{
|
{
|
||||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||||
@@ -331,6 +353,20 @@ void APS_Editor_PlayerController::StopSimulation()
|
|||||||
Brain->StopLogic(TEXT("PS_Editor"));
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "PS_Editor_TimelineSubsystem.h"
|
#include "PS_Editor_TimelineSubsystem.h"
|
||||||
#include "PS_Editor_SpawnManager.h"
|
#include "PS_Editor_SpawnManager.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
|
#include "AIController.h"
|
||||||
|
#include "BrainComponent.h"
|
||||||
#include "JsonObjectConverter.h"
|
#include "JsonObjectConverter.h"
|
||||||
#include "Misc/FileHelper.h"
|
#include "Misc/FileHelper.h"
|
||||||
#include "Misc/Paths.h"
|
#include "Misc/Paths.h"
|
||||||
@@ -160,7 +162,47 @@ bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_S
|
|||||||
TimelineSS->SetData(SceneData.Timeline);
|
TimelineSS->SetData(SceneData.Timeline);
|
||||||
if (!IsInEditorMode() && SceneData.Timeline.Tracks.Num() > 0)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "PS_Editor_SplineActor.h"
|
#include "PS_Editor_SplineActor.h"
|
||||||
#include "AIController.h"
|
#include "AIController.h"
|
||||||
#include "BrainComponent.h"
|
#include "BrainComponent.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "PS_Editor_SplineEditable.h"
|
#include "PS_Editor_SplineEditable.h"
|
||||||
#include "PS_Editor_TimelineSubsystem.h"
|
#include "PS_Editor_TimelineSubsystem.h"
|
||||||
#include "JsonObjectConverter.h"
|
#include "JsonObjectConverter.h"
|
||||||
@@ -348,6 +350,39 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
|||||||
Brain->StopLogic(TEXT("PS_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);
|
DisableAI(P);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "AIController.h"
|
#include "AIController.h"
|
||||||
#include "BrainComponent.h"
|
#include "BrainComponent.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -209,6 +211,40 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
|
|||||||
Brain->StopLogic(TEXT("PS_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
DisableAI(SpawnedPawn);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Components/ActorComponent.h"
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "Engine/EngineTypes.h"
|
||||||
#include "PS_Editor_SpawnableComponent.generated.h"
|
#include "PS_Editor_SpawnableComponent.generated.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,4 +53,15 @@ public:
|
|||||||
* Use scene loading filters to load only preload actors or exclude them. */
|
* Use scene loading filters to load only preload actors or exclude them. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable")
|
||||||
bool bPreload = false;
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user