Quick start — C++
Build dependencies
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine",
"GameplayTags",
"MotionWarping", // for the rider's warping component
"EDMountingCore",
"EDMountingVehicles" // only if you use AEDMountableVehicle
});
The rider
#include "Components/EDRiderComponent.h"
#include "MotionWarpingComponent.h"
AMyCharacter::AMyCharacter()
{
Rider = CreateDefaultSubobject<UEDRiderComponent>(TEXT("Rider"));
MotionWarping = CreateDefaultSubobject<UMotionWarpingComponent>(TEXT("MotionWarping"));
}
That is the entire rider-side setup. The interaction key:
void AMyCharacter::OnInteract()
{
const FEDMountResult Result = Rider->IsMounted()
? Rider->RequestDismount()
: Rider->RequestMountNearest();
if (!Result.bSuccess)
{
// Every refusal names itself, and every name has localised text.
ShowMessage(UEDMountingLibrary::GetFailureText(Result.Failure));
}
}
and the movement axis:
void AMyCharacter::OnMove(const FInputActionValue& Value)
{
const FVector2D Axis = Value.Get<FVector2D>();
if (Rider->IsSeated())
{
// A no-op unless this character is in a seat tagged Mount.Role.Driver.
Rider->ApplyDriveInput(Axis, FMath::Max(0.f, Axis.Y), FMath::Max(0.f, -Axis.Y), bHandbrakeHeld);
return;
}
// ... ordinary walking
}
Chaos holds the last input it was given. If you only forward while the key is held, releasing W leaves the car at full throttle.
The mount
#include "Components/EDMountableComponent.h"
#include "Components/EDMountPointComponent.h"
#include "Interfaces/EDMountableInterface.h"
#include "System/EDMountingTags.h"
class AMyVehicle : public AWheeledVehiclePawn, public IEDMountableInterface
{
GENERATED_BODY()
public:
AMyVehicle();
virtual void ApplyDriverInput_Implementation(APawn* Driver, const FVector2D& MoveInput,
float Throttle, float Brake, bool bHandbrake) override;
protected:
UPROPERTY(VisibleAnywhere) TObjectPtr<UEDMountableComponent> Mountable;
UPROPERTY(VisibleAnywhere) TObjectPtr<UEDMountPointComponent> DriverSeat;
};
AMyVehicle::AMyVehicle()
{
Mountable = CreateDefaultSubobject<UEDMountableComponent>(TEXT("Mountable"));
DriverSeat = CreateDefaultSubobject<UEDMountPointComponent>(TEXT("DriverSeat"));
DriverSeat->SetupAttachment(GetMesh());
DriverSeat->SeatName = TEXT("Driver");
DriverSeat->RoleTag = EDMountingTags::Mount_Role_Driver;
DriverSeat->SetRelativeLocation(FVector(30.f, -38.f, 55.f));
// A door on the near side. Leave EntryPoints empty and the framework derives a
// left/right pair from the vehicle's bounds, which is a fine starting point.
FEDEntryPoint LeftDoor;
LeftDoor.SideTag = EDMountingTags::Mount_Entry_Left;
LeftDoor.RelativeTransform = FTransform(FRotator::ZeroRotator, FVector(0.f, -115.f, -35.f));
DriverSeat->EntryPoints.Add(LeftDoor);
}
void AMyVehicle::ApplyDriverInput_Implementation(APawn* /*Driver*/, const FVector2D& MoveInput,
float Throttle, float Brake, bool bHandbrake)
{
UChaosVehicleMovementComponent* Movement = GetVehicleMovementComponent();
Movement->SetSteeringInput(MoveInput.X);
Movement->SetThrottleInput(Throttle);
Movement->SetBrakeInput(Brake);
Movement->SetHandbrakeInput(bHandbrake);
}
AEDMountableVehicle already has the mountable component, the driver's seat and the input forwarding.
Derive from it and you skip all of the above. See Vehicles.
Addressing a seat
Look it up once, keep the handle:
// At setup
GunnerSeat = Mountable->FindSeatByName(TEXT("Gunner"));
// Later, forever
Rider->RequestChangeSeat(GunnerSeat);
FEDSeatHandle is a stable id, valid on every machine, unchanged when seats come and go. It is the
only way to name a seat — see Seats for why that matters.
Reacting to things
Rider->OnMountPhaseChanged.AddDynamic(this, &AMyCharacter::OnPhaseChanged); // everywhere
Rider->OnMountFailed.AddDynamic(this, &AMyCharacter::OnMountFailed); // locally
Mountable->OnSeatOccupancyChanged.AddDynamic(this, &AMyHud::OnSeatChanged); // everywhere
Phase and occupancy fire on every machine, simulated proxies included, so a HUD showing who is in which seat needs no replication of its own.
Placing a pawn in a seat from script
FEDMountRequest Request;
Request.MountActor = Vehicle;
Request.DesiredSeat = Mountable->FindSeatByName(TEXT("Driver"));
Request.bAllowSeatFallback = false; // that seat or nothing
Request.bInstant = true; // no animation, and no distance check
Rider->RequestMountWithOptions(Request);
bInstant bypasses reach as well — an actor placed into a seat by script is not walking there. This is
the path for spawning already-seated, for save-game restore, and for cutscenes.
Next
- Seats and entry points — where most of the authoring happens.
- Replication — read it before shipping multiplayer.
- Extending — the hooks, and when to reach for each.