Throw At Gaze

Throwing in XR can be quite difficult, as described in our throwing use-case. Therefore we have implemented a reusable gaze-aware throwing component in Unreal.

We have also implemented an example scene in the SDK.

The [email protected] component internally uses a 2nd degree polynomial solver to try and figure out what velocity is needed to hit a target with a certain position, velocity and acceleration. It also has a complex path that will attempt to find the perfect throw apex to use to avoid environmental hazards that might block the throw, like a wall, or perhaps low roof inside a corridor.

To use the [email protected] component included in the SDK, simply attach it to some actor, we suggest your player controller and then fill its homing target using a Focus Manager.

  1. You can see the [email protected] component is added here.
  2. Here is the focus manager we are going to use for our throws.
  3. Here we set the throw target of the component using our focus manager.

When using the component, you in most cases want to use the CorrectThrow function:

	/**
	* This function is useful when throwing things in XR and you have a throw
	* vector from the player's attempt to throw something.  Don't forget to set 
	* collision on the thing you're throwing etc before using this though, otherwise 
	* tracing might not work. It will only correct trajectories where we can expect
	* a direct hit, or where we are simply out of range.
	*
	* @param ThrowOrigin				This is where the projectile will start.
	* @param OriginalThrowVector		This is the throw vector supplied by the developer
	*									to correct.
	* @param ThrowAngleThresholdDeg		If the angle difference between the OriginalThrowVector
										and the suggested vector is greater than this parameter,
										the vector will not be corrected. This is not applied
										if it is 0.
	* @param ThrowSpeedThreshold		If the speed difference between the OriginalThrowVector
										and the suggested vector is greater than this parameter,
										the vector will not be corrected. This is not applied
										if it is 0.
	* @param bUseSimple					If this is true, CalculateThrowAtGazeVectorSimple is 
										used in favor of the Complex version internally
	* @return							The potentially corrected throw vector
	*/
	UFUNCTION(BlueprintCallable, Category = "[email protected]")
	FVector CorrectThrow(
		const FVector& ThrowOrigin, 
		const FVector& OriginalThrowVector, 
		float ThrowAngleThresholdDeg, 
		float ThrowSpeedThreshold, 
		bool bUseSimple);

You can see it being used here in our XR pawn in the example scene:

And that is really all you have to do. If you have a look at the [email protected] API however, you can see that it is very customizable:

	/** This is the component that you're trying to hit with the throw. You can set this
	 *  on the same frame you are trying to throw, but setting it earlier will allow the
	 *  component to "aim" a bit better. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected]")
	UPrimitiveComponent* ThrowTarget;

	//Throw force applied to the throw vector can never be greater than this value.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected]")
	float MaxThrowSpeedCmPerSecs;

	//This is how high above the thrower the arc apex will be.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected]")
	float ThrowApexOffsetCm;

	/** If a solution could not be found with the current apex, the algorithm will try
	 *  to find a valid apex up to a maximum number of times equal to this value. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	int32 MaxNrTries;

	/** If this is true, the algorithm will prefer low apexes rather than mid or high ones
	 *  for a more natural looking throw. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	bool bPreferLowApexForTargetsBelowThrower;
	
	/** If this is true, the algorithm will attempt to trace the solution in the world
	 * to make sure nothing is in the way. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	bool bShouldTraceResult;

	//If bShouldTraceResult is true, an apex smaller than this value will never be tested
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	float ThrowApexOffsetMinimumCm;

	//If bShouldTraceResult is true, an apex larger than this value will never be tested
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	float ThrowApexOffsetMaximumCm;

	/** When testing different apexes, if the delta between the last test and the next one
	 is smaller than this value, tests will be aborted. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	float MinimumApexDeltaCm;

	/** If bShouldTraceResult is true, this is the size that will be used for tracing. 
	 * Should be larger or equal to the projectile size. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	float TraceRadiusCm;

	//If bShouldTraceResult is true, this is the length of each trace step done.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	int32 MaxNrTraceSteps;
	
	//If bShouldTraceResult is true, this is the length of each trace step done in seconds.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	float TraceStepSizeSecs;

	//This is the channel that will be used when tracing.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	TEnumAsByte<ECollisionChannel> TraceChannel;
	
	//If you have any additional actors that should be ignored by the tracing
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	TArray<AActor*> TraceIgnoreActors;

	//Let's you ignore this component's owner collision when tracing.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Complex")
	bool bShouldTraceIgnoreOwner;

	/** If this is false, the world gravity is used for calculations. If this is true,
	 *  CustomProjectileGravity is used instead. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Gravity")
	bool bUseCustomGravity;

	//If bUseCustomGravity is true, this is the gravity that will be used in calculations
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "[email protected] Gravity")
	FVector CustomProjectileGravity;

Like we mentioned in the introduction, the component has a complex path that tries to trace multiple arcs around a given wanted throw apex to try and avoid blocking geometry. As you might suspect, most of the properties in the “[email protected] Complex” category configure this behavior.

Please note that even the complex solver will at the moment not account for projectile bouncing and friction, so unless you implement that math yourself, you might want to scale the output vector slightly if you expect it to bounce and roll further after it impacts the ground.