Extending
Every hook below exists because the framework would otherwise have to guess at something only your game knows. None of them is required, and all of them are available in Blueprint.
The seams, at a glance
| Seam | Question it answers |
|---|---|
CanAcceptRider | May this rider take this seat? Runs on the server too. |
ApplyDriverInput | What does steering mean for this mount? |
SetSeatDoorOpen | How does this thing open a door? |
GetRiderTags | Where does this game keep the tags a locked seat checks? |
GetRiderMesh | Which of this pawn's meshes do people look at? |
UEDMountAnimationSet | Which animation, for which transition? |
FEDRiderPolicy | What happens to this rider's body while seated? |
Mount.Event.* tags | What else should happen at a named frame of a transition? |
A new moment in a transition
Declare a tag under Mount.Event, put an ED Mount Event notify carrying it on the frame it should
happen, and listen:
Rider->OnMountEvent.AddDynamic(this, &AMyCharacter::OnMountEvent);
void AMyCharacter::OnMountEvent(FGameplayTag EventTag, APawn* WhichRider)
{
if (EventTag == MyTags::Mount_Event_GrabHandle) { PlayHandSound(); }
}
The framework forwards anything it does not recognise, to both the rider and the mount, and does
nothing else with it. Mount.Event.Attach, .Detach and .Finished are the three it acts on itself.
A different animation for a case the fields do not cover
Context Query on an animation entry is an arbitrary tag query against the rider's tags, the mount's
tags and the request's tags together. A wounded character who climbs in slowly is a query against your
own injury tag, not a field added to the plugin.
That is the extension point that keeps the entry struct from growing one boolean per game.
A new kind of mount
There is nothing to do. See Turrets, horses and other mounts.
Reacting to occupancy from somewhere else
Mountable->OnSeatOccupancyChanged.AddDynamic(this, &AMyHud::OnSeatChanged);
Mountable->OnMountableChanged.AddDynamic(this, &AMyHud::OnMountableChanged);
Rider->OnMountPhaseChanged.AddDynamic(this, &AMyHud::OnPhaseChanged);
Rider->OnMountFailed.AddDynamic(this, &AMyHud::OnFailed);
Occupancy and phase fire on every machine, simulated proxies included, so a HUD showing who is in which seat needs no replication of its own. Failures fire only where the request was made.
An interaction prompt
The query is cheap enough to run every frame, because it walks a registry rather than the physics scene:
FEDMountCandidate Candidate;
if (UEDMountingLibrary::FindNearestMount(MyPawn, Query, Candidate))
{
// Candidate.MountActor, Candidate.Seat, Candidate.EntryTag, Candidate.Distance
ShowPrompt(Candidate.MountActor);
}
FEDMountQuery narrows it: MaxDistance, MaxViewAngle, bRequireFreeSeat,
bRequireReachableEntry, bPreferDriverSeat (decisive without a view cone, a tie-breaker with one — a
rider looking at a door has already chosen), and a MountTagQuery against the mountable's tags. The
first three reject before any capsule sweep, so narrow there rather than filtering the results.
Driving the transition from your own code
HandleMountEvent is public. Everything the framework does at a specific frame goes through it, so a
game that drives its transitions from a timeline, a Sequencer track or a state tree can call it
directly with Mount.Event.Attach and Mount.Event.Finished at the right moments.
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.
Where to be careful
CanAcceptRiderruns often. Once per seat per query, and the query may run every frame for a prompt. Do not trace in it.- Do not move a seat every frame in code. Parent it to something that moves instead; that is what makes it free.
- Do not write to
FEDMountStateyourself. It is the server's, and the sequence number is what keeps prediction honest. - Do not call
ReserveSeat/OccupySeat/ReleaseSeat. They are public C++ because the rider component needs them, not because gameplay should. Calling them desynchronises the rider's state from the seat's.