Skip to main content

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

SeamQuestion it answers
CanAcceptRiderMay this rider take this seat? Runs on the server too.
ApplyDriverInputWhat does steering mean for this mount?
SetSeatDoorOpenHow does this thing open a door?
GetRiderTagsWhere does this game keep the tags a locked seat checks?
GetRiderMeshWhich of this pawn's meshes do people look at?
UEDMountAnimationSetWhich animation, for which transition?
FEDRiderPolicyWhat happens to this rider's body while seated?
Mount.Event.* tagsWhat 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

  • CanAcceptRider runs 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 FEDMountState yourself. 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.