How to improve my PreditctProjectilePath (C++)

Hello there.
I am creating a personal Gameplay Action System. I already created some actions like throws, crouches, etc and now I am creating an action for aiming.
With this action, I want to visualize my projectile’s path when I will throw it.
What I already do is create the path and connected it with my socket, but there is few issues:

  • When I click the key for the InputAction, the path appear but then, after few seconds, it will disappear. I already tried to increase the Params.DrawDebugTime, but if I increase it too much, then after it will not disappear.
  • I want my path to start from the socket (and I achieved that) and finish where my mouse is pointing on the ground (not achieved). I already figured out something, but is clearly not working.

I leave here my .h file:

#pragma once

#include "CoreMinimal.h"
#include "SAction.h"
#include "SAction_AIM.generated.h"

class ASStyler;
/**
 * 
 */
UCLASS()
class RANGER_API USAction_AIM : public USAction
{
	GENERATED_BODY()
protected:

	UPROPERTY(VisibleAnywhere, Category = "AIM")
	FName HandSocket;

	UPROPERTY(EditAnywhere, Category = "AIM")
	float ProjectileRadius;

	UPROPERTY(EditAnywhere, Category = "AIM")
	float ProjectileVelocity;

	UPROPERTY(EditAnywhere, Category = "AIM")
	float AIMDelay;

	UPROPERTY(EditAnywhere, Category = "AIM")
	TSubclassOf<ASStyler> Styler;

	UFUNCTION()
	void AIMDelay_Elapsed(ACharacter* InstigatorCharacter);

public:

	virtual void StartAction_Implementation(AActor* Instigator) override;

	virtual void StopAction_Implementation(AActor* Instigator) override;

	USAction_AIM();
};

Here my .cpp file:

// Fill out your copyright notice in the Description page of Project Settings.


#include "SAction_AIM.h"

#include "PlayerCharacter.h"
#include "SStyler.h"
#include "Components/SplineComponent.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"

USAction_AIM::USAction_AIM()
{
	HandSocket = TEXT("Muzzle_01");
	ProjectileRadius = 500.0f;
	ProjectileVelocity = 2000.0f;
	AIMDelay = 0.1f;	
}


void USAction_AIM::StartAction_Implementation(AActor* Instigator)
{
	Super::StartAction_Implementation(Instigator);
	
	if (ensure(Instigator))
	{
		FTimerHandle TimerHandle_AIMDelay;
		FTimerDelegate Delegate;
		Delegate.BindUFunction(this, "AIMDelay_Elapsed", Instigator);

		GetWorld()->GetTimerManager().SetTimer(TimerHandle_AIMDelay, Delegate, AIMDelay, false);
	}

}

void USAction_AIM::StopAction_Implementation(AActor* Instigator)
{
	Super::StopAction_Implementation(Instigator);

	AActor* ActorInst = UGameplayStatics::GetActorOfClass(GetWorld(), Styler);
	ASStyler* StylerInst = Cast<ASStyler>(ActorInst);

	if (StylerInst)
	{
		StylerInst->GetSplineComponent()->ClearSplinePoints(true);
	}
	
}


void USAction_AIM::AIMDelay_Elapsed(ACharacter* InstigatorCharacter)
{
	if(ensure(InstigatorCharacter))
	{
		FTransform HandTransform = InstigatorCharacter->GetMesh()->GetSocketTransform(HandSocket);

		FPredictProjectilePathParams PredictParams;
		PredictParams.StartLocation = HandTransform.GetLocation();
		PredictParams.LaunchVelocity = HandTransform.GetRotation().GetForwardVector() * ProjectileVelocity;
		PredictParams.MaxSimTime = 2.0f;
		PredictParams.DrawDebugType = EDrawDebugTrace::ForDuration;
		PredictParams.ActorsToIgnore.Add(InstigatorCharacter);
		PredictParams.bTraceWithCollision = true;
		PredictParams.bTraceComplex = true;
		PredictParams.DrawDebugTime = 22.0f;

		FPredictProjectilePathResult PredictResult;
		
		bool bHit = UGameplayStatics::PredictProjectilePath(GetWorld(), PredictParams, PredictResult);

		if(bHit)
		{
			TArray<FVector> PointLocation;

			for (FPredictProjectilePathPointData PathPoint : PredictResult.PathData)
			{
				PointLocation.Add(PathPoint.Location);
			}

			FVector MouseLoc = FVector::Zero();
			FVector MouseDir = FVector::Zero();
			APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
			PC->DeprojectMousePositionToWorld(MouseLoc, MouseDir);

			FHitResult HitResult;
			bool bHitTrace = GetWorld()->LineTraceSingleByChannel(HitResult, MouseLoc, MouseLoc + MouseDir * 2000.0f, ECC_Visibility);
			if (bHitTrace)
			{
				PointLocation.Add(HitResult.Location);
			}

			AActor* ActorInst = UGameplayStatics::GetActorOfClass(GetWorld(), Styler);
			ASStyler* StylerInst = Cast<ASStyler>(ActorInst);

			if (StylerInst)
			{
				StylerInst->GetSplineComponent()->SetSplinePoints(PointLocation, ESplineCoordinateSpace::World);
			}
		}
		
	}
	
}

I am sorry if there are some big mistakes.
Thanks for the help in advice!

You didn’t post any of the code that actually matters, around the lifetime of the preview, or the code that draws the preview path.

Is this inside some other game framework? If it’s inside fortnite, for example, you should tag your post appropriately.

The code that create my ProjectilePath is visible in the post.
Like I said, I’m creating a Gameplay Action System, I create the class “Action_AIM” and inside my PlayerCharacter code I bind the action with the input, and I start the action by calling the class visible in the first post.
All the code that I use is there, with the “UGameplayStatics::PredictProjectilePath()” function.

Gotcha, for some reason I only saw a fraction of the C++ part.

In my experience, when things “stop working” after a few seconds, it’s usually garbage collection that collects something I don’t track correctly. However, actors that are in the world, aren’t automatically garbage collected, so that seems unlikely for your Styler object.
Also, having exactly one Styler object in the world, and querying the world for that instance each time you need it, isn’t ideal, for both performance and correctness reasons (it’s overhead to query for objects, and what if there’s two of them in the world?) In general, you’ll want the feedback components to live on the PlayerController (typically) rather than loose in the world. However, I’m also not seeing this as the cause of the symptoms you report.

I assume that your Styler object has a Spline component, and that this Spline is set to actually render something. If you depend only on the debug draw of the path prediction debug drawing, then that won’t stay on the screen, because you only call the start delegate once, when initiating the throw. If you expect your timer to keep firing, you should pass true to SetTimer().

Separately, for feedback for aiming, I’d expect it would feel weird to only update 10 times a second, I’d expect that to be done each frame.