Projectile pushing enemy during/after hit and being destroyed

Hello friends!

I have encountered a bug in my project that I just cannot figure out. Firing a projectile at an enemy will sometimes push them away, it happens more often while the enemy is running towards you and it also seems to occur more often with a lower projectile speed.


It can however still happen while the enemy is stationary.
The enemies have StaticMeshComponents with simulating physics enabled as all movement is physics based.

I feel like what I have is just a simple standard projectile using PMC and for the life of me I cannot tell what is wrong. Here’s the hit event of the base projectile.


My expected outcome is of course: the projectile hits and deals damage to the enemy and then gets destroyed, as you see I have disconnected the knockback node for testing purposes.

Any thoughts or ideas much appreciated! If you would like more information, please inform me.
Thanks.

Is there a reason for this? Why not rely on the Projectile Movement Component instead? If you enable physics, the component cannot do their job. Disabling physics gives it the chance to do its thing.

I feel like what I have is just a simple standard projectile using PMC

But it’s not since you simulate physics separately.

image

Not sure if that will solve it but that’s the very first thing I’d look into.

Hey!
The enemy is simulating physics, not the projectile itself.
The projectile actor is not simulating physics and using the Projectile Movement Component all the way.

1 Like

My bad, I jumped to conclusions.

Could you show / explain the component hierarchy of the enemy so we can better understand how they move then?


Also since it’s all physics based, you do not want physical interaction between the projectile and the enemy apart from detecting the hit?

Under regular circumstances, simulating bodies would interact and push one another around - that’s the whole point of using physics, right?

One thing that immediately comes to mind is to isolate it with channels and / or use overlaps.

1 Like

I do not quite recall how the enemy hierarchy is set up atm, I will have to explain it when I get back to the office.
What I can say is that the invisible Static Mesh is the only collider active, and it is moving around with velocity, no gravity and the movement is locked to the XY plane. The actor is not actually touching the ground.

I want the hit detection for the Hit struct, specifically the Hit normal atm. In the future I might want a physical interaction between a projectile and enemy as well.
I considered changing it to overlaps but I found the overlap “sweeping hit” is not recieved consistently and will therefore introduce a new bug when there is no Hit normal.

Regarding the physics push; I’ve tried setting the projectiles mass to 0,001 with no difference in effect. It seems to me that the projectile is placed inside the enemy and the enemy gets displaced as some sort of correction, that’s my best guess.
The enemies physics are otherwise used in many other aspects of the game.

Could you please elaborate on the “to isolate it with channels”?
Thanks.

That’s sounds very much like depenetration where intersecting geometry attempts being ejected.

Could you please elaborate on the “to isolate it with channels”?

You could have a collision volume on the player whose sole purpose is to detect getting hit. This would be separate from the element simulating physics which would ignore the projectiles and thus can never interact with any.

Allows you to choose who collides with whom with quite a bit of granularity.


But again, understanding fully how the characters move would help. We could see the running character so I immediately assumed a movement component moving the char - this seems to not be the case.

Here’s this enemys hierarchy, it inherits from Pawn, the mesh component is simulating physics and being moved around by the movement component with forces. The skeletal mesh is set to ignore all until it goes into ragdoll and during that time the mesh component is set to ignore all instead.
CrystalaMonsterComponents 2024-01-15 112642

Here’s the code for our custom FloatingPawnMovementComponent:

void UEnemyMovement::TickComponent(float DeltaTime, enum ELevelTick TickType,
                                   FActorComponentTickFunction* ThisTickFunction)
{
	if (ShouldSkipUpdate(DeltaTime))
	{
		return;
	}

	UPawnMovementComponent::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!PawnOwner || !UpdatedComponent)
	{
		return;
	}

	AController* Controller = PawnOwner->GetController();
	ABasePawn* basePawn = Cast<ABasePawn>(PawnOwner);
	if (Controller && Controller->IsLocalController() && basePawn)
	{
		// apply input for local players but also for AI that's not following a navigation path at the moment
		if (Controller->IsLocalPlayerController() == true || Controller->IsFollowingAPath() == false ||
			bUseAccelerationForPaths)
		{
			ApplyControlInputToVelocity(DeltaTime);
		}
		// if it's not player controller, but we do have a controller, then it's AI
		// (that's not following a path) and we need to limit the speed
		else if (IsExceedingMaxSpeed(basePawn->MoveSpeed.Get() * 10) == true)
		{
			Velocity = Velocity.GetUnsafeNormal() * basePawn->MoveSpeed.Get() * 10;
		}

		LimitWorldBounds();
		bPositionCorrected = false;

		AAIController* AIController = Cast<AAIController>(Controller);
		AActor* FocusActor = nullptr;
		if (AIController)
		{
			FocusActor = AIController->GetFocusActor();
		}

		float CosValue = FVector::DotProduct(Velocity.GetSafeNormal(), Controller->GetPawn()->GetActorForwardVector());
		if(!FocusActor)
		{
			if (CosValue < 0 && !FocusActor)
				Velocity = FVector(0, 0, 0);
			else
				Velocity *= CosValue; // Nice curve for speed / rotational difference from travel vector
		}
		


		// Rotate actor
		FQuat TargetRotation = DecideTargetRotation(Controller);
		FQuat Rotation = FQuat::Slerp(OldRotation, TargetRotation, TurningBoost / 2 * DeltaTime);
		RotationSpeed = Rotation.Rotator() - OldRotation.Rotator();
		
		// Move actor
		FVector Delta = Velocity * DeltaTime;

		if (!Delta.IsNearlyZero(1e-6f) || !Rotation.IsIdentity(1e-6f))
		{
			const FVector OldLocation = UpdatedComponent->GetComponentLocation();
			
			FHitResult Hit(1.f);

			APawn* pawn = Controller->GetPawn();
			UPrimitiveComponent* pc = Cast<UPrimitiveComponent>(pawn->GetRootComponent());
			if (pc)
			{
				ABasePawn* BasePawn = Cast<ABasePawn>(pawn);
				if ((BasePawn && BasePawn->IsMovementEnabled() && !Delta.IsNearlyZero(1e-6f)))
				{

				pc->AddForce(Delta * basePawn->MoveSpeed.Get() * 10, NAME_None, true);

					ABaseEnemy* enemy = Cast<ABaseEnemy>(pawn);
					if(enemy)
					{
						enemy->PlayMoveSound();
					}
				}
				if ((BasePawn && BasePawn->IsRotationEnabled()))
				{
					pawn->SetActorRotation(FRotator(Rotation));
					OldRotation = Rotation;
					OldRotation.Normalize();
				}
			}


			if (Hit.IsValidBlockingHit())
			{
				HandleImpact(Hit, DeltaTime, Delta);
			}

			// Update velocity
			// We don't want position changes to vastly reverse our direction (which can happen due to penetration fixups etc)
			if (!bPositionCorrected)
			{
				const FVector NewLocation = UpdatedComponent->GetComponentLocation();
				Velocity = ((NewLocation - OldLocation) / DeltaTime);
			}
		}

		// Finalize
		UpdateComponentVelocity();
	}
}

I personally do not understand everything going on here as a lot of it is copied from the in engine FloatingPawnMovement, we did however change the actual movement input to an AddForce() to allow the enemies movement to be affected by various physics interactions. I tried to highlight the specific code mention, just imagine it’s one block of code.

I read about depenetration and played with some settings, I changed the “Collision Max Push Out Velocity” to 0,001 and saw no difference in the problematic behaviour which is very confusing to me.

I have been too busy today but tomorrow I will try adding another collider which is not simulating physics specifically to absorb the projectile collisions.

Did I miss any important details?

Anyway, thanks for your time good sir!

I added an additional collider which is not simulating physics that only blocks projectiles.
BaseEnemyComponents 2024-01-15 112642

Interestingly this gives the same result as before:

Here’s the projectile collision settings:

I’m starting to think I’m gonna have to do some scuffed ray casting shenanigans.

Alright so I tried setting the projectiles to overlap and check for a hit value from a sphere trace when the overlaps did not generate sweeping hits to get a hit normal in all cases. However, the sphere trace did not hit and after a while I tried making a new collision profile for the trace specifically. Tracing by an overlapping profile does not generate trace hits, so that was a fun couple of hours :upside_down_face:

So my solution was:
Set projectiles to overlap
Create an additional collision profile for tracing
Get the hit normal from the sweeping hit if it occurs
Otherwise get the hit normal from a sphere trace
(Alternatively you can do a triple line trace, 1 from the middle of the hitbox, 1 from the left and 1 from the right)

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