This is a weird one, I can’t figure out why we need to do it this way, so any advice would be helpful. I’ll try to be as verbose as possible in this message.
We are basing this largely off of shooter code. We have Projectile and ImpactEffect classes. The server spawns the projectiles same as in the shooter code. When the server detects an impact (OnProjectileStop bound delegate) it calls OnImpact on the Projectile. It then calls Exploded only if it is the server. In Exploded it sets bExploded to true which is a replicated variable. The clients are supposed to receive this in the OnRep_Exploded function. This is only working for us if we spawn TracerFX on the server. The client doesn’t even need to create it unless we want to see the effect. But for some reason if the server never spawns it then the clients never receive the bExploded = true through the OnRep_Exploded. If that never gets called then the clients never see the ImpactEffect.
So does anyone know what causes the server to not send bExploded = true to the clients unless it spawns a particle system? For now the hack has been for the server to always spawn a particle system even if the clients don’t spawn one. I don’t like this solution though and would like to know what causes it and some hints that I may use to properly handle this.
Here are the things I have tried:
I have tried giving the projectile a static mesh, thinking it was a visual thing, it doesn’t change the behavior.
I have tried setting the replication on the projectile BP to be always relevant and it doesn’t change the behavior.
If I remove all particle system spawning on the server then no impact effects are ever shown.
Here is the code that we are using:
.h
/** did it explode? */
UPROPERTY(Transient, ReplicatedUsing = OnRep_Exploded)
bool bExploded;
UPROPERTY(EditDefaultsOnly, Transient, ReplicatedUsing = OnRep_IsTracer)
uint8 bIsTracer : 1;
/** [client] explosion happened */
UFUNCTION()
void OnRep_Exploded();
/** [client] bIsTracer was toggled */
UFUNCTION()
void OnRep_IsTracer();
/** handle hit */
UFUNCTION()
virtual void OnImpact(const FHitResult& HitResult);
/** trigger explosion */
void Explode(const FHitResult& Impact);
.cpp
//Client handler for when this projectile explodes, play FX
void AWAWProjectile::OnRep_Exploded()
{
FVector ProjDirection = GetActorRotation().Vector();
const FVector StartTrace = GetActorLocation() - ProjDirection * 200;
const FVector EndTrace = GetActorLocation() + ProjDirection * 150;
FHitResult Impact;
//If we do not have a line trace to the explosion, then it must have occurred right where our projectile is
if (!GetWorld()->LineTraceSingle(Impact, StartTrace, EndTrace, COLLISION_PROJECTILE, FCollisionQueryParams(TEXT("ProjClient"), true, Instigator)))
{
// failsafe
Impact.ImpactPoint = GetActorLocation();
Impact.ImpactNormal = -ProjDirection;
}
//Call the Explode function to display the FX
Explode(Impact);
}
//Client handler for when this projectile's bIsTracer bool switches
void AWAWProjectile::OnRep_IsTracer()
{
if (!TracerFX) { return; }
if (bIsTracer && !TracerPSC)
{
//Spawn the emitter at the location of the muzzle
TracerPSC = UGameplayStatics::SpawnEmitterAttached(TracerFX, RootComponent);
}
else if (!bIsTracer && TracerPSC)
{
TracerPSC->DeactivateSystem();
TracerPSC = NULL;
}
//TODO: the following was added to ensure that impact FX were called on clients, the following is
//TODO: only ever ran on the servers (this could cause problems with listen servers though always showing tracers).
//TODO: we should eventually figure out why impactFX are not working unless the server spawns this
else if (!bIsTracer)
{
TracerPSC = UGameplayStatics::SpawnEmitterAttached(TracerFX, RootComponent);
}
}
//Handle when this projectile impacted something
void AWAWProjectile::OnImpact(const FHitResult& HitResult)
{
//This projectile did not bounce and did not penetrate, so explode it
DeactivateFX();
//Only run this on the server and only if it has not exploded
if (Role == ROLE_Authority && !bExploded)
{
//Explode this projectile, play the particle FX and sound FX
Explode(HitResult);
//Disable it and then destroy it
DisableAndDestroy();
}
}
//Explodes this projectile when it impacts something
void AWAWProjectile::Explode(const FHitResult& Impact)
{
DeactivateFX();
//Figure out a location to explode this projectile, do not explode it inside
//of a mesh, otherwise no damage will result
// effects and damage origin shouldn't be placed inside mesh at impact point
const FVector NudgedImpactLocation = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
//If this weapon has explosion damage, radius, and a valid damage type then apply the damage radially (in a sphere)
if (WeaponConfig.ExplosionDamage > 0 && WeaponConfig.ExplosionRadius > 0 && WeaponConfig.DamageType)
{
UGameplayStatics::ApplyRadialDamage(this, WeaponConfig.ExplosionDamage, NudgedImpactLocation, WeaponConfig.ExplosionRadius, WeaponConfig.DamageType, TArray<AActor*>(), this, MyController.Get());
}
////Spawn any explosion effects if there is one
//if (ExplosionTemplate)
//{
// const FRotator SpawnRotation = Impact.ImpactNormal.Rotation();
// AWAWExplosionEffect* EffectActor = GetWorld()->SpawnActorDeferred<AWAWExplosionEffect>(ExplosionTemplate, NudgedImpactLocation, SpawnRotation);
// //Tell the explosion effect which material we impacted so that it can spawn the appropriate FX
// if (EffectActor)
// {
// EffectActor->SurfaceHit = Impact;
// UGameplayStatics::FinishSpawningActor(EffectActor, FTransform(SpawnRotation, NudgedImpactLocation));
// }
//}
//If we hit something then show the impact point
if (ImpactTemplate && Impact.bBlockingHit)
{
FHitResult UseImpact = Impact;
// trace again to find component lost during replication
if (!Impact.Component.IsValid())
{
const FVector StartTrace = Impact.ImpactPoint + Impact.ImpactNormal * 10.0f;
const FVector EndTrace = Impact.ImpactPoint - Impact.ImpactNormal * 10.0f;
FHitResult Hit = AWAWWeapon::WeaponTrace(GetWorld(), MyController.Get(), StartTrace, EndTrace);
UseImpact = Hit;
// UE_LOG(LogProjectile, Warning, TEXT("Impact point was not valid, recomputed impact point to spawn FX"));
}
//Spawn the bullet holes / FX at the impact point and set type of surface it hit
AWAWImpactEffect* EffectActor = GetWorld()->SpawnActorDeferred<AWAWImpactEffect>(ImpactTemplate, Impact.ImpactPoint, Impact.ImpactNormal.Rotation());
if (EffectActor)
{
EffectActor->SurfaceHit = UseImpact;
UGameplayStatics::FinishSpawningActor(EffectActor, FTransform(Impact.ImpactNormal.Rotation(), Impact.ImpactPoint));
}
}
//We will not explode more than once, so set this to true
bExploded = true;
}
Here is the projectile replication setup: