Can't seem to get PhysicsHandle->SetTargetLocation() to do anything

I do have the grabbing functionality from the course working just fine (even better after checking out this post), but what I’m trying to do is get other free-floating objects to follow another free-floating object I intend to grab and use as the key to open the path.

What I do is attach a MagneticBalls component I made to each free-floating object that I want to move towards a target object, as well as attaching a PhysicsHandle component.

In each MagneticBalls component in the editor, I set a target AActor that they should follow (exposed in the header file).

While I can get the PhysicsHandleComponent just fine, the current actor’s position, and the target actor’s position, using the physics handle to set target location doesn’t actually move the actor towards the target. Even if I manually move the target around, while all the location vectors are correctly updating, SetTargetLocation() still doesn’t actually move the actor.

What am I doing wrong?

MagneticBalls.cpp:

#include "MagneticBalls.h"


// Sets default values for this component's properties.
UMagneticBalls::UMagneticBalls()
{
    // Set this component to be initialized when the game starts, and to be ticked every frame.
    // You can turn these features off to improve performance if you don't need them.
    PrimaryComponentTick.bCanEverTick = true;
    // ...
}


// Called when the game starts
void UMagneticBalls::BeginPlay()
{
    Super::BeginPlay();

    GetPhysicsHandle();
    // Warning if destination object isn't attached via editor.
    if (!DestinationObject) {
        UE_LOG(LogTemp, Error, TEXT("'%s' is missing a destination object!"), *GetOwner()->GetName());
    }
    // ...
}


// Called every frame.
void UMagneticBalls::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    if (DestinationObject) {
        Elapsed += DeltaTime;

        if (Elapsed > FollowUpdateRate) {
            FindAndMoveToTarget();
            Elapsed = 0;
        }
    }
    // ...
}


/// Get physics handle component for this component's owner.
void UMagneticBalls::GetPhysicsHandle()
{
    BallPhysicsHandle = GetOwner()->FindComponentByClass<UPhysicsHandleComponent>();

    if (!BallPhysicsHandle) {
        UE_LOG(LogTemp, Error,
               TEXT("%s has no Physics Handle Component! Needed by MagneticBalls component."),
               *GetOwner()->GetName()
        );
    }
}


/// Get owner's location, get target's location, calculate distance, then set target location for physics handle.
void UMagneticBalls::FindAndMoveToTarget()
{
    // This object's current position.
    CurrentPosition = GetOwner()->GetTransform().GetLocation();
    // Our destination actor based on selection in editor.
    Destination = DestinationObject->GetTransform().GetLocation();
    // How far away this object is from the destination actor.
    Distance = GetOwner()->GetDistanceTo(DestinationObject);
    
    // TODO: This doesn't seem to do anything. <-------------
    BallPhysicsHandle->SetTargetLocation(Destination);
}

and the header file:

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PhysicsEngine/PhysicsHandleComponent.h"

#include "MagneticBalls.generated.h"


UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class ESCAPE_API UMagneticBalls : public UActorComponent
{
    GENERATED_BODY()

public:
    // Sets default values for this component's properties.
    UMagneticBalls();
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

protected:
    // Called when the game starts.
    virtual void BeginPlay() override;

private:
    /// Our Physics Handle Component added to the actor our MagneticBalls component goes on.
    UPROPERTY()
    UPhysicsHandleComponent* BallPhysicsHandle;
    /// The object we want our magnetic ball to move towards.
    UPROPERTY(EditAnywhere)
    AActor* DestinationObject;

    UPROPERTY(EditAnywhere)
    float FollowUpdateRate = 0.3f;
    float Distance;
    float Elapsed = 0;
    FVector Destination;
    FVector CurrentPosition;

    // Functions.
    void GetPhysicsHandle();
    void FindAndMoveToTarget();
};

Example of balls/objects I want moving towards each other.

Does anyone have any ideas what I’m missing or not understanding?

Hi

So, do you set each of the balls’ target AActor to be another ball?

Also, some guesses:

  • maybe try grabbing first?
  • you are trying to set one ball’s location to another ball’s location, maybe it is not working because they would collide if you do so?

Correct! At least for now. Really I’d love if I could figure out how to make each ball follow/move towards the closest identical object while obeying the physics engine (so they have acceleration, deceleration, bump into each other accurately, etc) so that they truly feel magnetic, but so far that’s beyond me. So for now I’m just assigning all of them to follow one CoreBall that doesn’t have the magneticball component on it.

I was wondering about that, if the only way to use PhysicsHandle is by having the CoreBall technically grab all the other balls I want to follow it, but it’s not exactly the approach I wanted to take since I’d eventually like them to be able to follow each other based on who is closest.

I tested that out with a more bruteforce initial approach where I just have them lerp towards the target’s position. They moved perfectly fine even though they would clip through the target and each other. The physics engine would eventually violently push them out of each other and it would fight with the movement logic, so it looked very buggy. So I was trying to approach it from the PhysicsHandle approach since the grabbing mechanic is very smooth.

I think there’s something I’m fundamentally not understanding about how the PhysicsHandle component works though.

In that case you just have to call grab function on the closest ball that you’ve found instead of the CoreBall. So, instead of setting manually your DestinationObject you could do something like:

DestinationObject = FindClosestBall(); // where FindClosestBall() returns the closest ball which we should follow
// and then do all PhysicsHandle stuff using that DestinationObject

So I’ve gone down a rabbit hole of trying to get this to work. I don’t think grabbing with the physics handle is the best approach, at least not from what I can tell so far with my extremely limited knowledge of UE functionality and how physics objects can be manipulated in C++.

What I’ve done is found (through Google and eventually the Unreal Engine community wiki) TObjectIterator and TActorIterator (either one can work, but TActorIterator is less prone to weirdness if you can use it apparently) for iterating through actors in the scene to find all the magnetic ball instances. The best way to identify them cleanly I’m still trying to figure out (maybe using tags?). But I’ve got it working, with some problems. It returns a TArray of AActor* pointers to our floating balls… Most of the time.

// -----------------------------------------------------------------------------
/// Retrieve all actors whose name starts with "MagneticBall". Return TArray.
/// \n Not very fool proof, but it works for now.
/// \n\n TODO: Replace current method with checking for component or tag instead.
TArray<AActor*> UMagneticBalls::GetAllMagneticBalls()
{
    FString ThisObjectName = *GetOwner()->GetName();
    TArray<AActor*> Balls;

    for (TActorIterator<AActor> Actor(GetWorld()); Actor; ++Actor) {
        // TODO: StaticMeshes are sliding into this list causing crashes later. Need to fix.
        // TODO: Try tags maybe?
        // TODO: Or maybe, if a StaticMesh is found, convert it to AActor with GetOwner().
        if (Actor->GetName().StartsWith("MagneticBall")) {

            // Skip itself to avoid marking itself as closest.
            if (Actor->GetName() == ThisObjectName) {
                continue;
            }
            // Make sure we add the pointer to the list, not the actual object.
            Balls.Add(*Actor);
        }
    }

    if (Balls.Num() == 0) {
        UE_LOG(LogTemp, Error, TEXT("Unable to find any balls. Did you name them MagneticBall?"));
    }

    return Balls;
}

I filter out when the ball finds itself (if GetOwner()->GetName() matches that ball actor’s name), because otherwise nothing would be closer than itself and all the balls would remain stationary.

Next I get the distance from the current actor to all the actors in that list, and store it in a custom UStruct called FBallDistances.


// -----------------------------------------------------------------------------
/// Calculate distances to all balls in level, and pair those distances up with each ball Actor pointer. \n\n
/// Return resulting TArray of FBallDistances (custom UStruct in header).
TArray<FBallDistances> UMagneticBalls::GetBallDistancePairs()
{
    // Make Array where we can store each ball pointer and its distance.
    TArray<FBallDistances> Dict;

    for (AActor* Ball : BallsInLevel) {
        if (Ball) {
            const float DistanceToBall = GetOwner()->GetDistanceTo(Ball);

            FBallDistances ThisBall;
            ThisBall.Ball = Ball;
            ThisBall.Distance = DistanceToBall;

            Dict.Add(ThisBall);
        }
        else {
            UE_LOG(LogTemp, Error, TEXT("Encountered null pointer. Expected pointer to MagneticBall actor."));
        }
    }

    return Dict;
}

Ultimately what I want to do is have each ball gravitate towards in between the two closest balls (more interesting arrangement than if they all just attach to each other), so I need to get the two closest balls to the current ball at any point.

// -----------------------------------------------------------------------------
/// Find the two closest balls to this actor. Return result.
TArray<AActor*> UMagneticBalls::FindClosestBalls(TArray<FBallDistances> ListOfBalls)
{
    FBallDistances Closest;
    FBallDistances SecondClosest;
    TArray<AActor*> Results;
    bool FirstCheck = true;

    for (FBallDistances Ball: ListOfBalls) {
        if (FirstCheck) {
            Closest.Ball = Ball.Ball;
            Closest.Distance = Ball.Distance;
            FirstCheck = false;
        }
        else if (Ball.Distance < Closest.Distance) {
            // Push 1st down to 2nd.
            SecondClosest.Ball = Closest.Ball;
            // Push newest up to 1st.
            Closest.Ball = Ball.Ball;
            Closest.Distance = Ball.Distance;
        }
    }

    if (!Closest.Ball) {
        UE_LOG(LogTemp, Warning, TEXT("Closest.Ball is null!!"))
    }
    if (!SecondClosest.Ball) {
        UE_LOG(LogTemp, Warning, TEXT("SecondClosest.Ball is null!!"))
    }

    // Order matters I think? We'll be calculating the midpoint vector between the two.
    Results.Add(Closest.Ball);
    Results.Add(SecondClosest.Ball);

    return Results;

Since I wasn’t really sure how to create a dictionary/hashmap in C++ or how to best approach it in UE4, I made a custom UStruct called FBallDistances where the first value “Ball” is the AActor* pointer, and the second value is “Distance” is a float, then I make a TArray of FBallDistances that pretty much functions as a key/value dictionary… more or less. This seems to work well enough, so far.

/// Contains AActor pointer to Ball, and it's distance in float.
USTRUCT()
struct FBallDistances
{
    GENERATED_BODY()
    UPROPERTY() AActor* Ball;
    float Distance;
};

Now that we have the two closest balls and their pointers directly, we can determine the vector we want to move to.

/// Set destination based on two closest balls.
FVector UMagneticBalls::SetDestination()
{
    BallsAndDistances = GetBallDistancePairs();
    ClosestBalls = FindClosestBalls(BallsAndDistances);
    AActor* BallOne = ClosestBalls[0];
    AActor* BallTwo = ClosestBalls[1];

    if (!BallOne) {
        UE_LOG(LogTemp, Error, TEXT("BallOne is null!"));
        return FVector(0, 0, 0);
    }

    if (!BallTwo) {
        UE_LOG(LogTemp, Error, TEXT("BallTwo is null!"));
        return FVector(0, 0, 0);
    }

    // TODO: For some reason, UStaticMesh* is getting into my AActor* arrays on some frames.
    // GetActorLocation() isn't for UStaticMesh so we sometimes crash here.
    FVector Result = (BallTwo->GetActorLocation() - BallOne->GetActorLocation()) / 2;
    return Result;
}

Now we can start moving the ball towards that vector. The thing is, I also want to be able to grab the ball with the regular PhysicsHandle in our Grabber component, so I had to make sure the ball isn’t trying to move while we’re grabbing it, or else this more direction translation of the balls position would win out over our Grabber PhysicsHandle movement every time and grabbing would not do anything substantial.

// Called every frame.
void UMagneticBalls::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    Elapsed += DeltaTime;
    CurrentPosition = GetOwner()->GetActorLocation();

    // Probably shouldn't traverse our array of balls every single frame.
    if (Elapsed > UpdateRate) {
        Elapsed = 0;
        CurrentPosition = GetOwner()->GetActorLocation();
        Destination = SetDestination();
    }

    FVector Smoothed = FMath::VInterpTo(CurrentPosition, Destination, DeltaTime, FollowSpeed);

    // All of this is to ensure we don't do access violations on null pointers, like when
    // the player isn't holding anything.
    if (PlayerHoldingItem()) {
        if (PlayerPhysicsHandle->GrabbedComponent->GetOwner()->GetName() != GetOwner()->GetName()) {
            // If the player isn't holding THIS object, continue to move.
            GetOwner()->K2_SetActorLocation(Smoothed, true, OUT Bump, false);
        }
    }
    else {
        // If the player isn't grabbing any object, continue to move.
        GetOwner()->K2_SetActorLocation(Smoothed, true, OUT Bump, false);
    }
}

I also found that the K2 version of SetActorLocation actually lets you do a sweep towards the destination, and can stop once it hits something. This way the balls don’t constantly clip into each other, or clip through walls on their way to the destination.

Once I get it to stop crashing at those “TODO” points, I’ll post a video to show how it looks! It’s really cool before it crashes or fails lol.

All right, I think I got the PhysicsHandle working. I did have to setup a grab function like you said.

I’m getting such weird results though haha I’ll have to make another post. I really more or less plugged in some PhysicsHandle grabbing logic into the above logic and the result is this.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms