So this is not going to be a super elegant solution, but it should work.
Create a new C++ class for your project:
Base you class on the ChaosVehicleMovementComponent:
You can call it whatever. I called the one in the following code MyChaosVehicleMovementComponent. You end up with a MyChaosVehicleMovementComponent.h and a MyChaosVehicleMovementComponent.cpp files.
In the .h file, we override the UpdateState function (which is the one fired on Tick on the original component, which is then calling the ServerUpdateState RPC). We also declare a new RPC, which is basically a copy of the ServerUpdateState, but we mark it unreliable instead of reliable.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ChaosVehicleMovementComponent.h"
#include "MyChaosVehicleMovementComponent.generated.h"
/**
*
*/
UCLASS()
class TESTPROJECT_API UMyChaosVehicleMovementComponent : public UChaosVehicleMovementComponent
{
GENERATED_BODY()
protected:
virtual void UpdateState(float DeltaTime) override;
/** Unreliable version of the original RPC, to avoid reliable RPC overflow. */
UFUNCTION(Unreliable, Server, WithValidation)
void ServerUpdateStateUnreliable(float InSteeringInput, float InThrottleInput, float InBrakeInput
, float InHandbrakeInput, int32 InCurrentGear, float InRollInput, float InPitchInput, float InYawInput);
};
Note: in the above, TESTPROJECT_API will be different for you, using the name of your project.
Then in the cpp file, we need to implement UpdateState. This is where this solution is a little bit dirty: we have to copy-paste the code from the original UpdateState function and just edit one line, to call our unreliable version of the RPC instead of the reliable one. If anyone has a better suggestion, please let us know, but here is the result:
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyChaosVehicleMovementComponent.h"
void UMyChaosVehicleMovementComponent::UpdateState(float DeltaTime)
{
// update input values
AController* Controller = GetController();
VehicleState.CaptureState(GetBodyInstance(), GetGravityZ(), DeltaTime);
VehicleState.NumWheelsOnGround = 0;
VehicleState.bVehicleInAir = false;
int NumWheels = 0;
if (PVehicleOutput)
{
for (int WheelIdx = 0; WheelIdx < PVehicleOutput->Wheels.Num(); WheelIdx++)
{
if (PVehicleOutput->Wheels[WheelIdx].InContact)
{
VehicleState.NumWheelsOnGround++;
}
else
{
VehicleState.bVehicleInAir = true;
}
NumWheels++;
}
}
VehicleState.bAllWheelsOnGround = (VehicleState.NumWheelsOnGround == NumWheels);
bool bProcessLocally = bRequiresControllerForInputs ? (Controller && Controller->IsLocalController()) : true;
// IsLocallyControlled will fail if the owner is unpossessed (i.e. Controller == nullptr);
// Should we remove input instead of relying on replicated state in that case?
if (bProcessLocally && PVehicleOutput)
{
if (bReverseAsBrake)
{
//for reverse as state we want to automatically shift between reverse and first gear
// Note: Removed this condition to support wheel spinning when rolling backwards with accelerator pressed, rather than braking
//if (FMath::Abs(GetForwardSpeed()) < WrongDirectionThreshold) //we only shift between reverse and first if the car is slow enough.
{
if (RawBrakeInput > KINDA_SMALL_NUMBER && GetCurrentGear() >= 0 && GetTargetGear() >= 0)
{
SetTargetGear(-1, true);
}
else if (RawThrottleInput > KINDA_SMALL_NUMBER && GetCurrentGear() <= 0 && GetTargetGear() <= 0)
{
SetTargetGear(1, true);
}
}
}
else
{
if (TransmissionType == Chaos::ETransmissionType::Automatic)
{
if (RawThrottleInput > KINDA_SMALL_NUMBER
&& GetCurrentGear() == 0
&& GetTargetGear() == 0)
{
SetTargetGear(1, true);
}
}
}
float ModifiedThrottle = 0.f;
float ModifiedBrake = 0.f;
CalcThrottleBrakeInput(ModifiedThrottle, ModifiedBrake);
// Apply Inputs locally
SteeringInput = SteeringInputRate.InterpInputValue(DeltaTime, SteeringInput, CalcSteeringInput());
ThrottleInput = ThrottleInputRate.InterpInputValue(DeltaTime, ThrottleInput, ModifiedThrottle);
BrakeInput = BrakeInputRate.InterpInputValue(DeltaTime, BrakeInput, ModifiedBrake);
PitchInput = PitchInputRate.InterpInputValue(DeltaTime, PitchInput, CalcPitchInput());
RollInput = RollInputRate.InterpInputValue(DeltaTime, RollInput, CalcRollInput());
YawInput = YawInputRate.InterpInputValue(DeltaTime, YawInput, CalcYawInput());
HandbrakeInput = HandbrakeInputRate.InterpInputValue(DeltaTime, HandbrakeInput, CalcHandbrakeInput());
if (!bUsingNetworkPhysicsPrediction)
{
// and send to server - (ServerUpdateState_Implementation below)
//ServerUpdateState(SteeringInput, ThrottleInput, BrakeInput, HandbrakeInput, GetCurrentGear(), RollInput, PitchInput, YawInput);
ServerUpdateStateUnreliable(SteeringInput, ThrottleInput, BrakeInput, HandbrakeInput, GetCurrentGear(), RollInput, PitchInput, YawInput);
}
if (PawnOwner && PawnOwner->IsNetMode(NM_Client))
{
MarkForClientCameraUpdate();
}
}
else if (!bUsingNetworkPhysicsPrediction)
{
// use replicated values for remote pawns
SteeringInput = ReplicatedState.SteeringInput;
ThrottleInput = ReplicatedState.ThrottleInput;
BrakeInput = ReplicatedState.BrakeInput;
PitchInput = ReplicatedState.PitchInput;
RollInput = ReplicatedState.RollInput;
YawInput = ReplicatedState.YawInput;
HandbrakeInput = ReplicatedState.HandbrakeInput;
SetTargetGear(ReplicatedState.TargetGear, true);
}
}
bool UMyChaosVehicleMovementComponent::ServerUpdateStateUnreliable_Validate(float InSteeringInput, float InThrottleInput, float InBrakeInput, float InHandbrakeInput, int32 InCurrentGear, float InRollInput, float InPitchInput, float InYawInput)
{
return ServerUpdateState_Validate(InSteeringInput, InThrottleInput, InBrakeInput, InHandbrakeInput, InCurrentGear, InRollInput, InPitchInput, InYawInput);
}
void UMyChaosVehicleMovementComponent::ServerUpdateStateUnreliable_Implementation(float InSteeringInput, float InThrottleInput, float InBrakeInput
, float InHandbrakeInput, int32 InCurrentGear, float InRollInput, float InPitchInput, float InYawInput)
{
ServerUpdateState_Implementation(InSteeringInput, InThrottleInput, InBrakeInput, InHandbrakeInput, InCurrentGear, InRollInput, InPitchInput, InYawInput);
}
The unreliable RPC is called on line 84.
We also implement our unreliable RPC, which is just calling the reliable RPC implementations under the hood. Compile and replace the vehicle component in Blueprints by your custom one.
This fix means that if the Chaos Vehicle plugin gets updated in the future (and you care about this update), the original UpdateState method code might change. So you may need to re-copy-paste the content in your override. You can find the source code here:
…\UE_5.4\Engine\Plugins\Experimental\ChaosVehiclesPlugin\Source\ChaosVehicles\Private\ChaosVehicleMovementComponent.cpp
In any case, whether you use this fix or not, as @Rev0verDrive mentioned, reducing the rate at which server updates occur is going to help.
I don’t have any experience with vehicle and physics replication, so I don’t know if the RPC being marked reliable is a requirement of this workflow. I guess you can just give the fix a go and see if things work as expected. In the code, the original reliable ServerUpdateState RPC is called only when you are not using the Network Physics Prediction plugin. My guess is that Epic assumes that for physics replication everyone is going to use this plugin, and the reliable RPC overflow isn’t really a concern, but I might be wrong.