RayCast is different Server vs Client!

When the player shoots he performs a RayCast that I show for debugging purposes. This is the code for the function StartFire()



FVector CameraLoc;
FRotator CameraRot;
Cast<APACharacter>(GetOwner())->GetActorEyesViewPoint(CameraLoc, CameraRot);

FVector ShootDir = CameraRot.Vector();
const float ProjectileAdjustRange = 10000.0f;
const FVector StartTrace = GetCameraDamageStartLocation(ShootDir);
const FVector EndTrace = StartTrace + (ShootDir * ProjectileAdjustRange);

FHitResult HitResult = WeaponTrace(StartTrace, EndTrace);

**DrawDebugLine(GetWorld(), Start, End, Color, true, -1, 0, 0.7f);         // this is the debug/visible RayCast line**


In order to fully understand the code above, you need **GetCameraDamageStartLocation() **and WeaponTrace() functions;



FVector APAWeapon::GetCameraDamageStartLocation(const FVector& AimDir) const
{
    APlayerController* PC = Cast<APACharacter>(GetOwner()) ? Cast<APlayerController>(Cast<APACharacter>(GetOwner())->Controller) : nullptr;
    FVector OutStartTrace = FVector::ZeroVector;

    if (PC)
    {
        FRotator UnusedRot;
        PC->GetPlayerViewPoint(OutStartTrace, UnusedRot);
    }

    return OutStartTrace;
}

************************************************************************

FHitResult APAWeapon::WeaponTrace(const FVector& StartPlug, const FVector& EndPlug, const TArray<AActor*>& IgnoreActors) const
{
    static FName WeaponFireTag = FName(TEXT("WeaponTrace"));

    // Perform trace to retrieve hit info
    FCollisionQueryParams TraceParams(WeaponFireTag, true, Instigator);
    TraceParams.bTraceAsyncScene = true;
    TraceParams.bReturnPhysicalMaterial = true;
    TraceParams.AddIgnoredActors(IgnoreActors);

    FHitResult Hit(ForceInit);
    GetWorld()->LineTraceSingleByChannel(Hit, StartPlug, EndPlug, COLLISION_WEAPON, TraceParams);

    return Hit;
}


Now, the screenshots:

SERVER

The Trace lines up so perfectly with the Camera that you don’t see it. If you move laterally, though, you’ll see the Trace.

**CLIENT


**

On the Client, though, it’s a bit out-of-phase. It’s not lined up like in case of the Server.

The function containing all of this is called from a Server RPC call of this kind:



if (Role < ROLE_Authority)
        ServerHandleFiring();

else
{
        ............ do the trace ............
}


Here ServerHandleFiring() is just the Server RPC function which calls **StartFire() **(this time, as the Server)



    UFUNCTION(reliable, server, WithValidation)
    void ServerHandleFiring();
    void ServerHandleFiring_Implementation();
    bool ServerHandleFiring_Validate();


**How can I solve this?

Is there a better way to do it?**

Thanks in advance!

Confused - Are you trying to resolve the raycast on the client or the server?

Network communication takes time - so you need to design an approach to account for that.

There are costs and benefits to both approaches.

I want the Client’s Raycast to behave like the Server’s!

I really can’t understand why the result is different since there are no variables involved that may change the end result…

Are the two pictures only for illustration? Because the camera views are very different so its not much of a surprise that the ray is visible on one and not the other.

If they are illustrations, have you verified that the input to the raycast methods are the same? Most network engines compress vectors and its not lossless. But this should be obvious by inspecting the variables.

I think I know what you’re going for, but you may run into issues with responsiveness and unsynchronised variables (as a result of latency). It’s entirely possible that the client’s view and the server’s view are not 100% the same. Are you replicating the client’s view to the server? If not, the server might have a different idea of the exact start and end positions of your line trace, based on its local representation of the client. However, replicating the client’s view isn’t entirely necessary (actually, you would typically pass the client’s local view data through an RPC whenever they want to fire, rather than making it a replicated variable) if you aren’t going for server-side hit detection (where the server processes all shots and hits. Ensures optimal experience for low-ping players, but really shafts anyone with slightly higher ping as it will feel unresponsive or inaccurate based on their view). It looks like you want to make a shooter. I usually perform line traces (weapon firing) like this to ensure that clients feel responsive (also known as client-side hit detection, server validation):

Perform the shot locally on the client (including visual effects), which generates a hit result. You then pass the hit result through to the server (along with the start trace vector and end trace vector) as an RPC. You can then do some clever stuff to verify the shot and whatnot to limit lag compensation, but you would mostly just process the hit and transmit the damage through to the hit actor. This client-side hit detection approach is used in Overwatch, for example. Games like Battlefield 1 use a hybrid approach where anyone with ping below 150 (I believe?) is given client-side hit detection rights, while anyone above 150 has their hit detection handled server-side (to avoid shooting low-ping players when they have moved behind cover on their screen). Like I mentioned, the server-side approach puts a major disadvantage on higher-ping players (they need to lead their shots more and fire ahead of where players are on their screen to receive a hit). This is why I personally prefer client-side hit detection. You might get shot when moving behind cover now and then by higher-ping players, but it ensures that the majority of players will have a good overall experience, especially if they’re playing from regions that don’t have local servers.

So, if you want to move to client-side hit detection instead of your server-side approach, here’s usually how I do it (if anyone else has a better approach, please let me know. I’m by no means an expert):
Perform your shot locally. Play the correct visual effects, anims, and whatnot. Then, pass the hit into an RPC function like this:

ServerVerifyShot_Implementation(const FHitResult& Hit, FVector ShotDirection, FVector TraceEnd)

Then, if you don’t want to perform extra verification, just apply the damage to the hit actor inside of here (and play the required visual effects if it isn’t a dedicated server)
UGameplayStatics::ApplyPointDamage(Hit.GetActor, Damage, ShotDirection, Hit, MyOwner->GetInstigatorController(), this, DamageType);

Replicating fire effects is a different challenge, but you can find that pretty easily on good ol’ Google. There are a few approaches.

Let me know if this helped!

Really, big thanks for the huuge answer. Thank you really really much.

I’ve tried performing the LineTrace on the client (as you suggested) as soon as the Fire() function is called (hence, as soon as the player clicks the left mouse button):



    FVector CameraLoc;
    FRotator CameraRot;
    Cast<APACharacter>(GetOwner())->GetActorEyesViewPoint(CameraLoc, CameraRot);

    FVector ShootDir = CameraRot.Vector();

    // trace from camera to check what's under crosshair
    const float ProjectileAdjustRange = Range;
    const FVector StartTrace = GetCameraDamageStartLocation(ShootDir);
    const FVector EndTrace = StartTrace + (ShootDir * ProjectileAdjustRange);
    TArray<AActor*> toIgnore;
    toIgnore.Add(Cast<APACharacter>(GetOwner()));

    ShowTrace(StartTrace, EndTrace, FColor::Red);


But the problem is exactly the same. The client’s line trace is visible and a bit off (like the screenshots above).

However, I kinda gave up to the idea of getting the exact same result for both the server and the client.

Your answer, though, was brilliant and will remain a future reference for me.

You’re super welcome! Good luck.