Bullets, BulletDrop, BulletTraveltime and Replication - Server load and lag [SOLVED]

Hello guys,
currently I’m spawning a bullet actor when a player shoots.
This makes it easy to simulate gravity and the travel speed of the bullet.

I “stress-tested” this by spawning a few hundred bullets with unlimited lifetime and I’m encountering a problem:

  • Let’s say there are currently a few hundred bullets in the air.
  • The player decides to shoot a wall.
  • The bullets hit the wall and explode, but it takes very long for the client to see the explosion effects.
  • The server sees everything correctly.

Now my questions:

  1. Why does it so long for the client to be able to see the effects but the server sees it instantly?
  2. Is there a more network efficient way to simulate bullet drop and travel time?
  3. How would I start?

Thanks for your time and help!

Edit:

My solution with the help of joeGraf

  • Player Shoots
  • I save the relevant properties (FVector TraceFrom, FVector TraceTo, FVector Velocity, float DeltaSeconds, float ElapsedTime) of every single bullet that is shot in a struct.
  • I call SimulateShot(FVector TraceFrom, FVector TraceTo, FVector Velocity, float DeltaSeconds, float ElapsedTime) that uses the values as parameters.

Here’s what I do in SimulateShot:

  • I use LineTraceSingleByChannel with TraceFrom and TraceTo

  • If the FHitResult is a bBlockingHit, I print which actor I’ve hit (I should and will probably call replication and damage here)

  • I change important values like that:

    // Bullet Drop
    TraceFrom = TraceTo;
    TraceTo = (Velocity * DeltaSeconds) + TraceFrom;
    Velocity += DeltaSeconds * ProjectileConfig.ProjectileDrop;
    DeltaSeconds = GetWorld()->GetDeltaSeconds();
    ElapsedTime += DeltaSeconds;

After that:

	if (!BulletHitResult.bBlockingHit && ElapsedTime <= ProjectileConfig.ProjectileLifeSpan)
	{
		BulletTimer(TraceFrom, TraceTo, Velocity, DeltaSeconds, ElapsedTime);
	}

	DrawDebugLine(this->GetWorld(), TraceFrom, TraceTo, FColor::Red, true, 10000, 30);

I stop the trace as soon as ElapsedTime hits the intended ProjectileLifespan which I save in another struct,
and I call a function which was the trickiest part for me - BulletTimer:

void ASWeaponProjectile::BulletTimer(FVector TraceFrom, FVector TraceTo, FVector Velocity, float DeltaSeconds, float ElapsedTime)
{
	FTimerHandle TimerHandle_BulletTimer;
	FTimerDelegate BulletDelegate = FTimerDelegate::CreateUObject(this, &ASWeaponProjectile::SimulateShot, TraceFrom, TraceTo, Velocity, DeltaSeconds, ElapsedTime);
	GetWorldTimerManager().SetTimer(TimerHandle_BulletTimer, BulletDelegate, DeltaSeconds, false);
}

I’m using a timer to call SimulateShot again every Tick

I haven’t tested this with high load yet but I think it should work since I don’t spawn any actors and the LineTrace gets stopped as soon as the ProjectileLifeSpan is reached.

I hope this is more network friendly and maybe my solution can help somebody with their problem or with other stuff.

It’s definitely not guaranteed to be in any way “good” or the solution.

  1. The server is authoritative so all of the game logic runs there. Because of that, there’s zero delay between when something happens and it appears on screen
  2. As you discovered hundreds of projectiles are going to be unreasonably slow to replicate. Best is to simulate the bullet travel and drop on the server and only tell the clients when the hit happens. Usually, bullets travel too fast too see, so there’s no advantage to using projectiles to simulate that.
  3. Start by looking at a hit scan weapon in either UT or in the ShooterGame example. Then add support for tracking where the bullets are over time on the server. Do the processing on the server and replicate the results to the clients.

First, thanks for your answer!
Second:
Before I had the system in place where I would spawn an actor every time I shoot, I had a simple LineTraceByChannel(). The direction vector of the EndTrace would be multiplied by DeltaTime and the Travelspeed, and over time I would subtract the Z-Vector to simulate gravity.

This worked “fine”.

Problem with that was that every
DeltaTick I had another hitresult and
I didn’t know how to get the “real”
hitresult which would allow me to
apply pointdamage to the actor that
has been hit.

Any idea on how to do that? (if that’s
what you think would be more network
efficient and if that’s what you mean
by “hit scan” :slight_smile: )

Edit: I think my only mistake with that I wanted to replicate every hit instead of only the last one, how dumb!
I will try the old way again and tweak it.

Thanks for your time!

Yep, I will definitely not spawn an actor then!
One last little question: Do I have to use a timer/tick for this, or is there a less resource demanding way to simulate the TravelTime and Bullet Drop of the bullet via LineTraceSingleByChannel()?

I wouldn’t spawn an actor for this, as that is really too heavy weight. I’d keep a list of bullets in flight and trace their movement over time each frame. Once you’ve resolved the bullet’s final result, then apply damage and replicate the results to the clients as needed

You’ll need some manager that handles it over time so you’ll need a tick function on that manager to do it.

So what if I wanted to have a lot of bullets without spawning actors but still have a particle trail left behind on every two or three bullets (to prevent them stacking up and becoming an eye sore), how would I move particle trails without spawning actors?
And what if the bullets were slower and i wanted them to be seen?

I’m having issues in my current multiplayer game with the projectiles causing major lag because I have many enemies firing their weapons and spawning 1 projectile every 0.3 seconds