Bi-directional trigger volume?

I’m looking for insights into setting up a trigger volume/actor/whatever that will trigger one thing when the player exits one side, and a different thing when the player exits the other side. Think of it like a plane; if the actor passes through the plane from side A to B, it will trigger event X, and if he passes from side B to A, it will trigger event Y.

Specifically, I’m doing a 2.5D segment of my game with splines which curve through 3D space to indicate the 2D plane the character is constrained to. This all works fine; but I have a “fork in the road” where I need to deactivate one spline and activate a different spline if the player jumps up onto a higher ledge.

I tried using a trigger volume with a flip-flop node, and two trigger volumes adjacent to one another, but both ultimately yield the same issue: it’s possible for the player to move forward into the trigger volume, then back up without passing THROUGH it, to activate the toggle. e.g. he can jump up to the platform to enter the branching path, walk back to the edge of it to trigger the transition back onto the main path, and then reverse direction while still ON the platform, thus following the primary path while in the space meant for the second one and spectacularly breaking everything.

Does anyone have any suggestions for this kind of “once you pass THROUGH the volume” switch, as opposed to the usual “once you TOUCH the volume” switch?

make a BP with two *overlapping *triggers.
introduce two variables which determine which of the two triggers you are overlapping atm:

overlapFront
overlapBack

on the OverlapStart/OverlapEnd you toggle those two flags.

together with the OverlapEnd event and the flag you know if the event is relevant and therefore you know which side the player leaves:

if overlapFront == false and EventOverlapEnd(Back)
–> player left at back end

if overlapBack == false and EventOverlapEnd(Front)
–> player left at front end

if overlapFront == true and EventOverlapEnd(Back)
if overlapBack == true and EventOverlapEnd(Front)
–> player went from one trigger to the other, but didn’t leave the area yet

Works excellent in theory but in practice this doesn’t solve the problem at all, presumably due to inconsistency in how UE4 handles “simultaneous” overlap events.

It’s still possible to walk directly into the trigger volume, turn around, and wade out again and trigger the wrong message, and I suspect the reason is because both EndOverlaps occur within the same frame, so the engine just processes them sequentially based on whatever trigger happens to be higher in the event stack.

I had post same solution but deleted as did it, but with two triggers, one overlapping the other on a side. When you exit the big one, you check with bool that you overlap the second one (set true on enter and false on exit). If yes event x if no event y.

if i understand well , you want to detect when the exit the trigger from the left and when from the right
you can try this

on end overlap -> if (otherActor.location.x>trigger.location.x) -> exit from right
else ->exit from left

MHouse that’s a perfect solution.

I ended up tweaking it a bit so it could work in any world-space orientation (I find the delta between the actor’s rotation and the look-at rotation between it and the player to determine if the player EndOverlaps nearer the front 180° or the back 180°) but it’s consistent and reliable.

Yep the way I do it is with 2 triggers next to each other. So I only want a trigger event to happen if i come in from the left hand side so I set a boolean to true when i hit the left trigger and then the right trigger only calls the function if it’s true. Then you can do the same thing in reverse.

Of course the overlapping space would have to be bigger than the character itself to avoid this.
But then Mhousse1247’s solution is much better anyway :slight_smile:

You could also use the dot product between the pawn velocity and the trigger forward vector. The result, positive or negative values, will tell you which direction you are exiting the volume.

thanks, you just saved my college’s final project :heart:
for those who want a three-dimensional solution of @Mhousse1247

1 Like

I agree with PaleoNeo that Dot Product is by far the best way. I found however there to always be the issue of character rotation, and find that if the character is rotating (say turning around) as you move into the trigger you will get an unexpected result when comparing the characters forward vector or velocity, to the trigger forward vector. The way I fixed is by looking from the triggers perspective, and getting the look vector from the trigger to the character, this removes rotation from the mix and you can easily distinguish using dot product whether to one side or the other.

On BeginOverlap:

BeginVector = (OtherActor->GetActorLocation() - GetActorLocation()).GetSafeNormal2D();

On EndOverlap:

FVector EndVector = (OtherActor->GetActorLocation() - GetActorLocation()).GetSafeNormal2D();
float DotProduct = EndVector | BeginVector;

Then simply {using psuedo code}
if (DotProduct < 0.0f) { character is moving same direction as that entered, i.e. gone left to right, do X; }
if (DotProduct >= 0.0f) { character is moving in opposite direction to that entered, i.e. gone left to left, do Y; }

You could do the same in Blueprint easily enough.

NOTE: you can use FVector::DotProduct() rather than the C++ | symbol for dotproduct. I normalized the vectors as I just want direction not magnitude. I also only care about the X & Y axis hence using GetSafeNormal2D(), but you can use GetSafeNormal() which takes into account the Z axis.

To update this topic - to ensure a trigger works perfectly you need to ask “what side of the triggers plane are the points on”, as I have found a few scenarios where measuring the dotproduct etc fails, as relative to a 2D line and not a 3D plane.

So need to ask is point A and point B on same side of the triggers plane or different. You do this using the plane equation: ax+by+c*z+d=0. Once you work this out not so scary, I have tried to explain below in a simple way.

  1. Calculate the plane using the above equation. Use the planes normal, its location, and its distance from the world origin:

float Plane = NormalPlane.X * PlaneLocation.X + NormalPlane.Y * PlaneLocation.Y + NormalPlane.Z * PlaneLocation.Z + Dist;

Where:
FVector NormalPlane = this->GetActorRightVector(); //or forward vector depending on trigger width (i.e. if longer in X or Y)
FVector PlaneLocation = this->GetActorLocation();
float Dist = PlaneLocation.Length();

  1. Substitute the location of the point to compare into the equation, using its Location, the planes Normal and the planes distance the the origin. This gives the distance from and direction from the plane.

float PointAPlane = NormalPlane.X * PointA.X + NormalPlane.Y * PointA.Y + NormalPlane.Z * PointA.Z + Dist;

Where:
FVector PointA = OtherActor->GetActorLocation();
float Dist = PlaneLocation.Length();

  1. by subtracting these you will get a negative or positive value.
    i.e. Plane - PointAPlane = neg or positive value

  2. compare the sign, if both the same they lie on the same side of the triggers plane, if different on different sides.

bTriggerSuccess = signbit(Plane - PointAPlane) == signbit(Plane - PointBPlane) ? false : true;

You can do some extra maths to simplify and also simplify the code but this works & demos the principle:

Here it is in code using a single function, a BeginOverlapEvent (to get PointA) and an EndOverlapEvent (to get PointB).

BeginOverlap(AActor* OtherActor)
{
BeginDistFromPlane = CalcDistFromPlane(OtherActor);
}

EndOverlap(AActor* OtherActor)
{
bTriggerSuccess = signbit(BeginDistFromPlane) == signbit(CalcDistFromPlane(OtherActor)) ? false : true;
}

CalcDistFromPlane(AActor* OtherActor)
{
if (!OtherActor) { return 0.0f; }
FVector NormalPlane = this->GetActorRightVector(); //or forward vector
FVector PointA = OtherActor->GetActorLocation();
FVector PlaneLocation = this->GetActorLocation();
float Dist = PlaneLocation.Length(); //constant d = distance from origin
return (NormalPlane.X * PlaneLocation.X + NormalPlane.Y * PlaneLocation.Y + NormalPlane.Z * PlaneLocation.Z + Dist) - (NormalPlane.X * PointA.X + NormalPlane.Y * PointA.Y + NormalPlane.Z * PointA.Z + Dist);
}

I’d be interested to know any thoughts on this - but my testing indicates it works and no longer has the issues of being related to a 2D line.

I have implemented this in my Dynamic Weather system for sale on FAB and done extensive testing: Ex Ultra Dynamic Weather System | Fab