Skip to main content

Vehicles

EDMountingVehicles is the Chaos bridge: everything that is true of a wheeled vehicle and of nothing else. It is a separate module so a project that mounts only horses and turrets never links a physics vehicle solver. Delete it from the .uplugin and the rest of the framework still builds — that is the test the split has to pass.

The easy way

Derive from AEDMountableVehicle. It is an AWheeledVehiclePawn that already has:

  • a mountable component,
  • a driver's seat tagged Mount.Role.Driver, parented to the vehicle mesh,
  • ApplyDriverInput wired to the Chaos movement component,
  • the handbrake pulled when the driver's seat empties.

A Blueprint deriving from it is a mesh, wheels and a torque curve — the same work as the engine's own vehicle template, and the mounting comes free. Add passenger seats by adding more ED Mount Point components; they register themselves.

PropertyDefault
Stop When DriverlesstrueCut the throttle and pull the handbrake when the driver's seat empties.
Driver Seat SocketnoneSnap the driver's seat to a mesh socket at begin play.
Why Stop When Driverless defaults on

Chaos holds the last input it was given. A driver who gets out at speed otherwise leaves behind a car still at full throttle with the wheels turned, which drives itself into the nearest wall.

Two things it does for you that are easy to miss

AEDMountableVehicle turns on physics simulation for the vehicle mesh, and tells Chaos not to require a possessing controller. A vehicle needs both, and neither is discoverable:

  • AWheeledVehiclePawn ships with bSimulatePhysics off. The engine's own vehicle template turns it on in the Blueprint, so a vehicle written in C++ inherits a car that cannot move.
  • Chaos throws away the input of an unpossessed pawn. Under the default possession policy the driver keeps their own controller, so the car has none — and every ApplyDriverInput is discarded a frame later. Turning the requirement off is right rather than a workaround: the question it exists to answer, may this input drive this vehicle, was already answered at the seat.

Either one alone gives a car that revs and does not move, with nothing in the log to say why. If you implement the interface on a vehicle of your own instead of deriving from AEDMountableVehicle, do both yourself.

The other way

A vehicle that already derives from something else needs six things, four of which are required:

// 1. Implement the interface.
class AMyVehicle : public AMyVehicleBase, public IEDMountableInterface { ... };

// 2. A mountable component and at least one seat.
Mountable = CreateDefaultSubobject<UEDMountableComponent>(TEXT("Mountable"));

DriverSeat = CreateDefaultSubobject<UEDMountPointComponent>(TEXT("DriverSeat"));
DriverSeat->SetupAttachment(GetMesh());
DriverSeat->RoleTag = EDMountingTags::Mount_Role_Driver;

// 3. Make the vehicle drivable at all — see above; both of these are required.
GetMesh()->SetSimulatePhysics(true);
GetVehicleMovementComponent()->SetRequiresControllerForInputs(false);

// 4. Forward the driver's input.
// Where the car is simulated: the server, and the driver's own client, which predicts ahead of
// the server. A spectating client gets the call too, for its wheel and lights, and moves nothing.
void AMyVehicle::ApplyDriverInput_Implementation(APawn* Driver, const FVector2D& MoveInput,
float Throttle, float Brake, bool bHandbrake)
{
if (!HasAuthority() && !(Driver && Driver->IsLocallyControlled()))
{
return;
}
UChaosVehicleMovementComponent* Movement = GetVehicleMovementComponent();
Movement->SetSteeringInput(MoveInput.X);
Movement->SetThrottleInput(Throttle);
Movement->SetBrakeInput(Brake);
Movement->SetHandbrakeInput(bHandbrake);
}

// 5. Optional: stop when the driver leaves.
void AMyVehicle::OnRiderLeft_Implementation(APawn* Rider, FEDSeatHandle Seat) { ... }

// 6. Optional: doors, driven by anim notifies in the get-in montage.
void AMyVehicle::SetSeatDoorOpen_Implementation(FEDSeatHandle Seat, bool bOpen) { ... }

Steps 1 to 4 are about twenty-five lines. All of it is available in Blueprint through the ED Mountable Interface.

Why throttle and brake are separate

A car needs both at once — a single signed axis cannot express braking while still rolling forward, which happens every time a player taps the brake at speed. Steering comes from MoveInput.X so one 2D axis binding drives the whole car.

Doors

Mark a window in the get-in montage with the ED Mount Event Window notify state, tagged Mount.Event.DoorOpen and Mount.Event.DoorClose. The framework calls SetSeatDoorOpen at both ends, on every machine, with nothing replicated — because every machine is playing the same montage. The door opens on the frame the animator decided the hand reaches it.

The seat handle is passed through, so a vehicle with four doors knows which one.

You do not need a montage for this. With no animation authored, the framework opens the door for the length of the transition and closes it at the end — see Animation. The example project's car is exactly that: four doors on hinges, driven entirely by the procedural transition.

Passengers

Under the default possession policy, a passenger keeps their own controller, camera and abilities. ApplyDriveInput is a no-op for anyone whose seat is not tagged Mount.Role.Driver, so a passenger holding W moves nothing even if their input bindings are still live — you do not have to unbind anything when someone sits down.

Aircraft, boats, anything else

Nothing in the core assumes wheels. ApplyDriverInput gives you a 2D axis, a throttle, a brake and a boolean; what they mean is your movement component's business. See Non-vehicle mounts.