Robot Nav Path Spline Updates

- Robots now generate the spline based on their input type
- There is now a curve generated with the spline with speed values for any distance along the spline
This commit is contained in:
Vyktori 2025-04-21 11:14:12 -04:00
parent b0fec0cc7a
commit a028ed8caf
10 changed files with 335 additions and 12 deletions

Binary file not shown.

View File

@ -3,14 +3,13 @@
#include "Gameplay/Robot/Components/Movement/LRMC_Stretch.h"
#include "Actors/MujocoVolumeActor.h"
#include "Gameplay/Robot/LuckyRobotPawnBase.h"
void ULRMC_Stretch::PerformMovement(float DeltaTime, const ERobotInputHandlingMethod InputHandlingMethod, const ERobotInputType InputType, const FVector2D& InputValues)
{
Super::PerformMovement(DeltaTime, InputHandlingMethod, InputType, InputValues);
constexpr float TurnMod = 0.1f;
// we can not do this because the rear actuator is not being used, so Turn would always override Move every frame with a value of 0.f
//Move(InputValues.X);
//Turn(InputValues.Y * TurnMod);
@ -38,7 +37,7 @@ void ULRMC_Stretch::PerformMovement(float DeltaTime, const ERobotInputHandlingMe
}
else
{
Turn(InputValues.Y * TurnMod);
Turn(InputValues.Y);
}
break;
case ERobotInputType::Pathfinding:
@ -54,7 +53,7 @@ void ULRMC_Stretch::PerformMovement(float DeltaTime, const ERobotInputHandlingMe
case ERobotInputType::Turn:
{
PreviousInputType = ERobotInputType::Turn;
Turn(InputValues.Y * TurnMod);
Turn(InputValues.Y);
}
break;
case ERobotInputType::Pathfinding:
@ -75,7 +74,8 @@ void ULRMC_Stretch::Turn(const float InValue) const
{
if (GetMujocoVolumeActor())
{
GetMujocoVolumeActor()->SetActuatorValueByIndex(0, -InValue);
GetMujocoVolumeActor()->SetActuatorValueByIndex(1, InValue);
const float TurnMod = GetOwningRobot() ? GetOwningRobot()->GetTurnModifier() : 1.f;
GetMujocoVolumeActor()->SetActuatorValueByIndex(0, InValue * -TurnMod);
GetMujocoVolumeActor()->SetActuatorValueByIndex(1, InValue * TurnMod);
}
}

View File

@ -0,0 +1,192 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Gameplay/Robot/Data/RobotSettings.h"
#include "Components/SplineComponent.h"
#include "Gameplay/Robot/LuckyRobotPawnBase.h"
#include "Kismet/KismetMathLibrary.h"
void FRobotSplinePath::GenerateSplinePath(ALuckyRobotPawnBase* InRobot, const FRobotNavPath& InNavPath)
{
if (IsValid(InRobot) && !InNavPath.NavPoints.IsEmpty())
{
Robot = InRobot;
NavPath = InNavPath;
if (GetRobot())
{
USplineComponent* OldSplinePath = GetSplinePath();
if (UActorComponent* NewComp = GetRobot()->AddComponentByClass(USplineComponent::StaticClass(), false, FTransform::Identity, false))
{
if (USplineComponent* NewSplineComp = Cast<USplineComponent>(NewComp))
{
NewSplineComp->ClearSplinePoints();
NewSplineComp->SetAbsolute(true, true, true);
int32 PointIndex = 0;
for (auto const& NavPoint : NavPath.NavPoints)
{
NewSplineComp->AddSplinePointAtIndex(NavPoint.Location, PointIndex, ESplineCoordinateSpace::World, false);
PointIndex++;
}
NewSplineComp->UpdateSpline();
SplinePath = NewSplineComp;
RegenerateSplinePathValues();
// begin debug
if (GetSplinePath())
{
GetSplinePath()->SetVisibility(true);
GetSplinePath()->SetHiddenInGame(false);
}
// end debug
}
}
if (IsValid(OldSplinePath))
{
OldSplinePath->DestroyComponent();
}
}
}
}
void FRobotSplinePath::RegenerateSplinePathValues()
{
if (GetRobot() && GetSplinePath())
{
constexpr float SplinePredictionTime = 1.f;
constexpr float SplinePredictionDistanceMin = 64.f;
const float SpeedLimit = GetRobot()->RobotSettings.SpeedLimit;
const float TargetSpeed = FMath::Min<float>(GetRobot()->GetTargetSpeed(), SpeedLimit);
const float SplinePredictionDistance = FMath::Max<float>(SplinePredictionTime * TargetSpeed, SplinePredictionDistanceMin);
constexpr float TangentClamp = 128.f;
const int32 SplinePoints = GetSplinePath()->GetNumberOfSplinePoints();
const bool bSingleInput = GetRobot()->RobotSettings.InputHandlingMethod == ERobotInputHandlingMethod::SingleInput;
if (bSingleInput)
{
for (int32 x = 0; x < SplinePoints; x++)
{
GetSplinePath()->SetTangentAtSplinePoint(x, FVector::ZeroVector, ESplineCoordinateSpace::Local, false);
}
}
else
{
for (int32 x = 0; x < SplinePoints; x++)
{
if (x < SplinePoints)
{
GetSplinePath()->SetSplinePointType(x, ESplinePointType::CurveCustomTangent, false);
const FVector PointLoc1 = GetSplinePath()->GetSplinePointAt(x - 1, ESplineCoordinateSpace::World).Position;
const FVector PointLoc2 = GetSplinePath()->GetSplinePointAt(x, ESplineCoordinateSpace::World).Position;
const FVector PointLoc3 = GetSplinePath()->GetSplinePointAt(x + 1, ESplineCoordinateSpace::World).Position;
const float Distance1 = FVector::Dist(PointLoc1, PointLoc2);
const float Distance2 = FVector::Dist(PointLoc2, PointLoc3);
constexpr float PointDistanceMod = 0.33f;
const float TangentMin = FMath::Min<float>(SplinePredictionDistance * 10.f, TangentClamp);
const FVector ArriveTangent = (PointLoc3 - PointLoc1).GetSafeNormal() * FMath::Min<float>(Distance1 * PointDistanceMod, TangentMin);
const FVector LeaveTangent = (PointLoc3 - PointLoc1).GetSafeNormal() * FMath::Min<float>(Distance2 * PointDistanceMod, TangentMin);
UE_LOG(LogTemp, Warning, TEXT("RegenerateSplinePathValues ArriveTangent (%s)"), *ArriveTangent.ToString());
UE_LOG(LogTemp, Warning, TEXT("RegenerateSplinePathValues LeaveTangent (%s)"), *LeaveTangent.ToString());
GetSplinePath()->SetTangentsAtSplinePoint(x, ArriveTangent, LeaveTangent, ESplineCoordinateSpace::World, false);
}
}
}
GetSplinePath()->UpdateSpline();
const float SplineLength = GetSplinePath()->GetSplineLength();
SplinePathSpeedModifier.EditorCurveData.Reset();
constexpr float MinSpeedMod = 0.2f;
constexpr float SplinePrecision = 10.f;
if (bSingleInput)
{
constexpr float SectionSpeedMod = 0.4f;
const float SectionSpeedClamp = FMath::Min<float>(SpeedLimit * SectionSpeedMod, TargetSpeed);
const float DecelerationStep = SplinePredictionDistance + SplinePrecision;
SplinePathSpeedModifier.EditorCurveData.AddKey(0.f, SectionSpeedClamp);
SplinePathSpeedModifier.EditorCurveData.AddKey(SplineLength, TargetSpeed * MinSpeedMod);
const float FirstSectionEnd = GetSplinePath()->GetDistanceAlongSplineAtSplinePoint(1);
if (FirstSectionEnd >= DecelerationStep)
{
SplinePathSpeedModifier.EditorCurveData.AddKey(SplinePrecision, TargetSpeed);
}
for (int32 y = 1; y < SplinePoints; y++)
{
const float SectionStart = GetSplinePath()->GetDistanceAlongSplineAtSplinePoint(y - 1);
const float SectionEnd = GetSplinePath()->GetDistanceAlongSplineAtSplinePoint(y);
const float SectionDistance = SectionEnd - SectionStart;
if (SectionDistance >= DecelerationStep)
{
SplinePathSpeedModifier.EditorCurveData.AddKey(SectionEnd - SplinePredictionDistance, SectionSpeedClamp);
SplinePathSpeedModifier.EditorCurveData.AddKey(SectionEnd - DecelerationStep, TargetSpeed);
}
else
{
SplinePathSpeedModifier.EditorCurveData.AddKey(SectionStart, SectionSpeedClamp);
}
SplinePathSpeedModifier.EditorCurveData.AddKey(SectionEnd, SectionSpeedClamp);
if (y < SplinePoints)
{
const float NextSectionEnd = GetSplinePath()->GetDistanceAlongSplineAtSplinePoint(y + 1);
const float NextSectionDistance = NextSectionEnd - SectionEnd;
if (NextSectionDistance >= DecelerationStep)
{
SplinePathSpeedModifier.EditorCurveData.AddKey(SectionEnd + SplinePrecision, TargetSpeed);
}
}
}
}
else
{
const int32 SplineIntervals = UKismetMathLibrary::SafeDivide(SplineLength, SplinePrecision);
for (int32 z = SplineIntervals; z >= 0; z--)
{
if (z == 0)
{
SplinePathSpeedModifier.EditorCurveData.AddKey(0.f, TargetSpeed * MinSpeedMod);
}
else if (z == SplineIntervals)
{
SplinePathSpeedModifier.EditorCurveData.AddKey(SplineLength, TargetSpeed * MinSpeedMod);
}
else
{
constexpr float MaxSpeedMod = 1.f;
const float SplineDistance = z * SplinePrecision;
const float DistanceToPathEnd = SplineLength - SplineDistance;
const float PathEndMod = DistanceToPathEnd <= SplinePredictionDistance ? FMath::Max<float>(UKismetMathLibrary::SafeDivide(DistanceToPathEnd, SplinePredictionDistance), 0.f) : 1.f;
const FVector IntervalTangent = GetSplinePath()->GetTangentAtDistanceAlongSpline(SplineDistance, ESplineCoordinateSpace::Local);
const float IntervalCurve = FMath::Min<float>(IntervalTangent.Length(), TangentClamp);
const float IntervalAlpha = UKismetMathLibrary::SafeDivide(IntervalCurve, TangentClamp);
const float SpeedMod = (MaxSpeedMod - IntervalAlpha) * PathEndMod;
const float ClampedSpeed = FMath::Min<float>(SpeedLimit * SpeedMod, SpeedLimit * MinSpeedMod);
SplinePathSpeedModifier.EditorCurveData.AddKey(SplineDistance, ClampedSpeed);
}
}
}
// begin debug
const int32 SplineIntervals = UKismetMathLibrary::SafeDivide(SplineLength, SplinePrecision);
for (int32 z = SplineIntervals; z >= 0; z--)
{
UE_LOG(LogTemp, Warning, TEXT("SpeedCurveValue [%f :: %f]"), z * SplinePrecision, SplinePathSpeedModifier.EditorCurveData.Eval(z * SplinePrecision));
}
// end debug
}
}

View File

@ -56,8 +56,6 @@ void ALuckyRobotPawnBase::InitializeMovementComponent()
void ALuckyRobotPawnBase::BeginPlay()
{
Super::BeginPlay();
InitializeMujocoVolumeActor();
InitializeMovementComponent();
@ -66,7 +64,10 @@ void ALuckyRobotPawnBase::BeginPlay()
GetLuckyWorldSubsystem()->RegisterLuckyRobot(this);
GetLuckyWorldSubsystem()->TargetSpeedChanged.AddUniqueDynamic(this, &ALuckyRobotPawnBase::SetTargetSpeed);
GetLuckyWorldSubsystem()->RobotNavPathChanged.AddUniqueDynamic(this, &ALuckyRobotPawnBase::SetRobotNavPath);
}
Super::BeginPlay();
}
bool ALuckyRobotPawnBase::HasMovementValue() const
@ -123,6 +124,8 @@ void ALuckyRobotPawnBase::SetupPlayerInputComponent(UInputComponent* PlayerInput
EnhancedInputComponent->BindAction(TurnAction, ETriggerEvent::Completed, this, &ALuckyRobotPawnBase::TurnStop);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ALuckyRobotPawnBase::Look);
EnhancedInputComponent->BindAction(TogglePathfindingAction, ETriggerEvent::Started, this, &ALuckyRobotPawnBase::TogglePathfinding);
}
}
@ -250,6 +253,12 @@ void ALuckyRobotPawnBase::Look(const FInputActionValue& Value)
UE_LOG(LogTemp, Warning, TEXT("ALuckyRobotPawnBase :: Input :: Look (%s)"), *Value.Get<FVector2D>().ToString());
}
void ALuckyRobotPawnBase::TogglePathfinding(const FInputActionValue& Value)
{
PreviousInputType = ERobotInputType::None;
CurrentInputType = IsPathfinding() ? ERobotInputType::None : ERobotInputType::Pathfinding;
}
void ALuckyRobotPawnBase::RemoveMujocoVolumeActor()
{
if (GetMujocoVolumeActor())
@ -276,6 +285,18 @@ ULuckyWorldSubsystem* ALuckyRobotPawnBase::GetLuckyWorldSubsystem()
return LuckyWorldSubsystem.Get();
}
void ALuckyRobotPawnBase::SetRobotNavPath(const FRobotNavPath& InRobotNavPath)
{
CurrentSplinePath.GenerateSplinePath(this, InRobotNavPath);
Internal_RobotNavPathChanged(CurrentSplinePath);
}
void ALuckyRobotPawnBase::Internal_RobotNavPathChanged(const FRobotSplinePath& NewNavPath)
{
RobotNavPathChanged(NewNavPath);
}
int32 ALuckyRobotPawnBase::GetTaskCount()
{
if (GetLuckyRobotsGameInstance())
@ -344,6 +365,8 @@ void ALuckyRobotPawnBase::SetTargetSpeed(const float InTargetSpeed)
void ALuckyRobotPawnBase::Internal_TargetSpeedChanged(const float OldValue)
{
GetCurrentNavPath().RegenerateSplinePathValues();
TargetSpeedChanged(TargetSpeed, OldValue);
}

View File

@ -72,4 +72,14 @@ void ULuckyWorldSubsystem::UpdateTorqueLimiter(const float InTorqueLimiter)
{
TorqueLimiterChanged.Broadcast(Robot_TorqueLimiter);
}
}
}
void ULuckyWorldSubsystem::UpdateRobotNavPath(const FRobotNavPath& InRobotNavPath)
{
RobotNavPath = InRobotNavPath;
if (RobotNavPathChanged.IsBound())
{
RobotNavPathChanged.Broadcast(RobotNavPath);
}
}

View File

@ -48,6 +48,7 @@ protected:
FRobotSettings RobotSettings = FRobotSettings();
//virtual void EvaluateRobotPath();
virtual void InterpolateSpeedValues(const float DeltaTime, const float Acceleration, const float Deceleration);
FVector2D CurrentSpeedValues = FVector2D::ZeroVector;

View File

@ -6,6 +6,8 @@
#include "RobotSettings.generated.h"
class ALuckyRobotPawnBase;
class USplineComponent;
class ULuckyRobotMovementComponent;
class AMujocoVolumeActor;
@ -30,6 +32,9 @@ USTRUCT(BlueprintType)
struct FRobotSettings
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Settings")
float SpeedLimit = 15.f;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Settings")
TSubclassOf<AMujocoVolumeActor> MujocoSettings;
@ -39,4 +44,54 @@ struct FRobotSettings
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Settings")
ERobotInputHandlingMethod InputHandlingMethod = ERobotInputHandlingMethod::CombinedInput;
};
USTRUCT(BlueprintType)
struct FRobotNavPoint
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Nav Point")
FVector Location = FVector::ZeroVector;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Nav Point")
bool bDestination = false;
};
USTRUCT(BlueprintType)
struct FRobotNavPath
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "Robot Nav Path")
TArray<FRobotNavPoint> NavPoints;
};
USTRUCT(BlueprintType)
struct FRobotSplinePath
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Robot Spline Path")
TWeakObjectPtr<ALuckyRobotPawnBase> Robot;
UPROPERTY(BlueprintReadOnly, Category = "Robot Spline Path")
FRobotNavPath NavPath = FRobotNavPath();
UPROPERTY(BlueprintReadOnly, Category = "Robot Spline Path")
TWeakObjectPtr<USplineComponent> SplinePath;
UPROPERTY(BlueprintReadOnly, Category = "Robot Spline Path")
FRuntimeFloatCurve SplinePathSpeedModifier = FRuntimeFloatCurve();
ALuckyRobotPawnBase* GetRobot() const { return Robot.Get(); }
void GenerateSplinePath(ALuckyRobotPawnBase* InRobot, const FRobotNavPath& InNavPath);
void RegenerateSplinePathValues();
USplineComponent* GetSplinePath() const { return SplinePath.Get(); }
FRobotSplinePath() {}
FRobotSplinePath(ALuckyRobotPawnBase* InRobot, const FRobotNavPath& InNavPath)
{
GenerateSplinePath(InRobot, InNavPath);
}
};

View File

@ -45,11 +45,11 @@ public:
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
float GetFastEndTaskTime();
UFUNCTION(BlueprintCallable, Category = "Lucky Robot Pawn")
UFUNCTION()
bool SetActuators(const TArray<FStretchRobotActuator>& InActuators);
// SPEED CONFIG
UFUNCTION(BlueprintCallable, Category = "Lucky Robot Pawn")
UFUNCTION()
void SetTargetSpeed(const float InTargetSpeed = 0.f);
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
@ -64,7 +64,7 @@ public:
ULuckyRobotMovementComponent* GetRobotMovementComponent() const { return RobotMovementComponent.Get(); }
// TORQUE LIMITER
UFUNCTION(BlueprintCallable, Category = "Lucky Robot Pawn")
UFUNCTION()
void SetTorqueLimiter(const float InTorqueLimiter = 0.f);
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
@ -116,6 +116,18 @@ public:
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
ULuckyWorldSubsystem* GetLuckyWorldSubsystem();
// NAV ROBOT PATH CHANGED
UFUNCTION()
void SetRobotNavPath(const FRobotNavPath& InRobotNavPath);
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
FRobotSplinePath& GetCurrentNavPath() { return CurrentSplinePath; }
void Internal_RobotNavPathChanged(const FRobotSplinePath& NewNavPath);
UFUNCTION(BlueprintImplementableEvent, Category = "Lucky Robot Pawn")
void RobotNavPathChanged(const FRobotSplinePath& NewNavPath);
// INPUTS
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
UEnhancedInputComponent* GetEnhancedInputComponent() const { return EnhancedInputComponent.Get(); }
@ -132,6 +144,9 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* TogglePathfindingAction;
void MoveStart(const FInputActionValue& Value);
void Move(const FInputActionValue& Value);
void MoveStop(const FInputActionValue& Value);
@ -142,6 +157,8 @@ public:
void Look(const FInputActionValue& Value);
void TogglePathfinding(const FInputActionValue& Value);
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
ERobotInputType GetCurrentInputType() const { return CurrentInputType; }
@ -154,6 +171,9 @@ public:
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
float GetDecelerationRate() const { return DecelerationRate; }
UFUNCTION(BlueprintPure, Category = "Lucky Robot Pawn")
float GetTurnModifier() const { return TurnModifier; }
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Lucky Robot Pawn")
float AccelerationRate = 1.5f;
@ -163,11 +183,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
float TargetSpeed = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
float TurnModifier = 1.f;
protected:
virtual void BeginPlay() override;
bool HasMovementValue() const;
bool HasTurnValue() const;
bool IsPathfinding() const { return CurrentInputType == ERobotInputType::Pathfinding; }
void RemoveMujocoVolumeActor();
@ -187,4 +211,7 @@ protected:
float TorqueLimiter = 0.85f;
int32 CurrentTaskIndex = -1;
float TaskTime = 0.f;
UPROPERTY()
FRobotSplinePath CurrentSplinePath = FRobotSplinePath();
};

View File

@ -3,6 +3,7 @@
#pragma once
#include "CoreMinimal.h"
#include "Gameplay/Robot/Data/RobotSettings.h"
#include "Subsystems/WorldSubsystem.h"
#include "LuckyWorldSubsystem.generated.h"
@ -15,6 +16,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLuckyRobotPawnReadyDelegate, ALucky
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLuckyRobotCurrentTaskChangedDelegate, const int32, NewTaskIndex);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLuckyRobotTargetSpeedChangedDelegate, const float, NewTargetSpeed);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLuckyRobotTorqueLimiterChangedDelegate, const float, NewTorqueLimiter);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLuckyRobotNavPathChangedDelegate, const FRobotNavPath&, NewRobotNavPath);
UCLASS()
class LUCKYWORLDV2_API ULuckyWorldSubsystem : public UWorldSubsystem
@ -65,11 +67,24 @@ public:
UPROPERTY(BlueprintAssignable, Category = "Lucky World Subsystem")
FLuckyRobotTorqueLimiterChangedDelegate TorqueLimiterChanged;
// ROBOT NAV PATH
UFUNCTION(BlueprintCallable, Category = "Lucky World Subsystem")
void UpdateRobotNavPath(const FRobotNavPath& InRobotNavPath);
UFUNCTION(BlueprintPure, Category = "Lucky World Subsystem")
FRobotNavPath GetRobotNavPath() const { return RobotNavPath; }
UPROPERTY(BlueprintAssignable, Category = "Lucky World Subsystem")
FLuckyRobotNavPathChangedDelegate RobotNavPathChanged;
protected:
TWeakObjectPtr<ALuckyRobotPawnBase> LuckyRobotPawn;
TWeakObjectPtr<AMujocoVolumeActor> MujocoVolumeActor;
UPROPERTY()
FRobotNavPath RobotNavPath = FRobotNavPath();
float Robot_TargetSpeed = 0.f;
float Robot_TorqueLimiter = 0.85f;
int32 Robot_CurrentTaskIndex = -1;