Issues with Using ChaosVehicles Plugin in Unreal 5.6 C++
Problem Background
While using the ChaosVehicles plugin in Unreal Engine 5.6 C++, I’ve encountered some unresolved issues.
Since a Vehicle is a Pawn, when we want to control a vehicle, we must have the PlayerController possess the VehiclePawn and attach the Character to the VehiclePawn:
PlayerController->Possess(VehiclePawn);
Character->AttachToActor(VehiclePawn, AttachmentRules, SocketName);
Character->DisableCollision();
graph LR
PlayerController -->|Possess| VehiclePawn
Character -->|Attach| VehiclePawn
Desired Game Design Pattern
However, many modern games implement vehicle functionality differently:
- RV There Yet? (made with Unreal Engine 5)
- GTA Series
- Red Dead Redemption Series
- Many open-world games
Their vehicle functionality works like this:
graph LR
PlayerController -->|Possess| Character
Character -->|Possess| VehiclePawn
Character -->|Attach| VehiclePawn
The specific behavior is: after the player controls the Character to enter a vehicle, the player still controls the Character, still views through the Character’s camera, and can still use the Character’s functions like interaction, free look, etc., but certain Character functions are disabled, such as walking, jumping, etc.
My Solution: VehicleControllerComponent
To achieve this, I created a VehicleControllerComponent that inherits from UActorComponent and can only be attached to a Character. The purpose is to give the Character the ability to control vehicles. Why not attach it to the PlayerController? Because it’s not the PlayerController that has the ability to control vehicles, but the Character. The PlayerController controls the Character, and the Character has the ability to control vehicles, thus giving the player indirect control over vehicles.
VehicleControllerComponent Implementation Details
-
Automatically gets the
Characterand binds inputs inBeginPlay:TWeakObjectPtr<ACharacter> Character = nullptr; void UVehicleControllerComponent::BeginPlay() { Super::BeginPlay(); Character = Cast<ACharacter>(GetOwner()); SetupInput(); } -
VehicleControllerComponenthas aControlVehicleAPI that automatically queries for theVehicleMovementComponent:TWeakObjectPtr<APawn> VehiclePawn = nullptr; TWeakObjectPtr<UChaosVehicleMovementComponent> VehicleMovementComponent = nullptr; bool USingularisVehicleControllerComponent::ControlVehicle(APawn* NewVehiclePawn) { if (!NewVehiclePawn) return false; UChaosVehicleMovementComponent* NewVehicleMovementComponent = NewVehiclePawn->FindComponentByClass<UChaosVehicleMovementComponent>(); if (!IsValid(NewVehicleMovementComponent)) return false; VehiclePawn = NewVehiclePawn; VehicleMovementComponent = NewVehicleMovementComponent; return true; } -
Key binding callbacks (Throttle example only):
void UVehicleControllerComponent::HandleThrottle(const FInputActionValue& Value) { if (!VehicleMovementComponent.IsValid()) return; VehicleMovementComponent->SetThrottleInput(Value.Get<float>()); VehicleMovementComponent->SetBrakeInput(0.0f); }
This implementation worked well for my single-player needs. I really like this VehicleControllerComponent approach, but unfortunately, if it had completely solved my requirements, I wouldn’t be seeking help here.
Multiplayer Issues
When I added multiplayer functionality to my game, I encountered unsolvable problems:
- A
Vehicleis aPawn. OnNM_ListenServer, theVehiclehas the roleROLE_Authority, but onNM_ClienttheVehiclehas the roleROLE_SimulatedProxy. Therefore, when a client player calls Input functions onVehiclePawn(likeSetThrottleInput) through theVehicleControllerComponent, theVehiclePawndoesn’t move becauseROLE_SimulatedProxyhas no authority.
Attempted Solutions
Solution 1: Forwarding Input via Server RPC
I made the VehicleControllerComponent replicable (SetIsReplicatedByDefault(true);) and forwarded input from NM_Client to NM_ListenServer, letting the VehicleControllerComponent on NM_ListenServer control the vehicle:
void HandleThrottle(const FInputActionValue& Value);
UFUNCTION(Server, Reliable)
void HandleThrottle_Server(const float& Throttle);
void VehicleControllerComponent::HandleThrottle(const FInputActionValue& Value)
{
if (!VehicleMovementComponent.IsValid()) return;
HandleThrottle_Server(Value.Get<float>());
}
void VehicleControllerComponent::HandleThrottle_Server_Implementation(const float& Throttle)
{
VehicleMovementComponent->SetThrottleInput(Throttle);
}
This worked! But it’s a poor solution and not best practice. The issues are:
- No movement prediction on the client
- In multiplayer games, the player driving the vehicle should have a local-like driving experience, while other players have a slightly worse experience due to network latency
- In this solution, the driving player needs to forward input to the server, which then replicates the vehicle’s state back to the player, resulting in no local-like driving experience
- This driving experience is unacceptable to players
Solution 2: Overriding VehicleMovementComponent
I extended UChaosWheeledVehicleMovementComponent and overrode various functions to break the forced binding between VehicleMovementComponent and Pawn, allowing VehicleMovementComponent to work even when attached to an Actor:
USingularisWheeledVehicleMovementComponent::USingularisWheeledVehicleMovementComponent()
{
bRequiresControllerForInputs = false;
SetIsReplicatedByDefault(true);
}
void USingularisWheeledVehicleMovementComponent::PreTickGT(const float DeltaTime)
{
// Super::PreTickGT(DeltaTime);
// movement updates and replication
if (PVehicleOutput && UpdatedComponent)
UpdateState(DeltaTime);
//......
}
Now VehicleMovementComponent can be attached to an Actor and work.
When I was about to test this in multiplayer, I realized that breaking the original logic would make the code difficult to maintain and goes against its design philosophy. Additionally, VehicleActor would lack many features compared to VehiclePawn, such as AIController support.
Conclusion and Request for Help
These are the issues I’ve encountered while using the Unreal 5.6 C++ ChaosVehicles plugin and the solutions I’ve attempted. I’m very grateful to the Unreal Engine community and Epic Games for providing such powerful tools and support.
I would greatly appreciate any suggestions or solutions to these problems. I hope this issue can be resolved. Thank you very much!